From 6235deb57113fa1ec44cb046f9154da5da162789 Mon Sep 17 00:00:00 2001 From: Joshua Bahirvani Date: Sat, 5 Sep 2026 18:23:03 +0530 Subject: [PATCH] drift_dataset --- .../versionized_gt/D1/README.md | 153 ++++ .../versionized_gt/D1/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C1/cdk/package.json | 15 + .../D1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C1/commit.json | 26 + .../versionized_gt/D1/commits/C1/diff.patch | 0 .../D1/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C10/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C10/cdk/package.json | 15 + .../D1/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C10/commit.json | 20 + .../versionized_gt/D1/commits/C10/diff.patch | 13 + .../versionized_gt/D1/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C2/cdk/cdk.ts | 426 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C2/cdk/package.json | 15 + .../D1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C2/commit.json | 20 + .../versionized_gt/D1/commits/C2/diff.patch | 23 + .../versionized_gt/D1/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C3/cdk/package.json | 15 + .../D1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C3/commit.json | 20 + .../versionized_gt/D1/commits/C3/diff.patch | 22 + .../versionized_gt/D1/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C4/cdk/cdk.ts | 440 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C4/cdk/package.json | 15 + .../D1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C4/commit.json | 20 + .../versionized_gt/D1/commits/C4/diff.patch | 14 + .../versionized_gt/D1/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C5/cdk/cdk.ts | 445 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C5/cdk/package.json | 15 + .../D1/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C5/commit.json | 20 + .../versionized_gt/D1/commits/C5/diff.patch | 16 + .../versionized_gt/D1/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C6/cdk/cdk.ts | 452 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C6/cdk/package.json | 15 + .../D1/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C6/commit.json | 20 + .../versionized_gt/D1/commits/C6/diff.patch | 18 + .../versionized_gt/D1/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C7/cdk/cdk.ts | 452 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C7/cdk/package.json | 15 + .../D1/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C7/commit.json | 20 + .../versionized_gt/D1/commits/C7/diff.patch | 13 + .../versionized_gt/D1/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C8/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C8/cdk/package.json | 15 + .../D1/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C8/commit.json | 20 + .../versionized_gt/D1/commits/C8/diff.patch | 15 + .../versionized_gt/D1/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/D1/commits/C9/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../D1/commits/C9/cdk/package.json | 15 + .../D1/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/D1/commits/C9/commit.json | 20 + .../versionized_gt/D1/commits/C9/diff.patch | 13 + .../versionized_gt/D1/commits/provenance.json | 152 ++++ .../versionized_gt/D1/metadata.yaml | 160 ++++ .../versionized_gt/D1/repository | 1 + .../versionized_gt/D1/synth_context.json | 1 + .../versionized_gt/D1/template_analysis.json | 486 ++++++++++++ .../versionized_gt/D2/README.md | 141 ++++ .../versionized_gt/D2/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/D2/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../D2/commits/C1/cdk/package.json | 15 + .../D2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C1/commit.json | 26 + .../versionized_gt/D2/commits/C1/diff.patch | 0 .../versionized_gt/D2/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/D2/commits/C2/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../D2/commits/C2/cdk/package.json | 15 + .../D2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C2/commit.json | 20 + .../versionized_gt/D2/commits/C2/diff.patch | 35 + .../versionized_gt/D2/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/D2/commits/C3/cdk/cdk.ts | 449 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../D2/commits/C3/cdk/package.json | 15 + .../D2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C3/commit.json | 20 + .../versionized_gt/D2/commits/C3/diff.patch | 22 + .../versionized_gt/D2/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/D2/commits/C4/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../D2/commits/C4/cdk/package.json | 15 + .../D2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C4/commit.json | 20 + .../versionized_gt/D2/commits/C4/diff.patch | 18 + .../versionized_gt/D2/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/D2/commits/C5/cdk/cdk.ts | 461 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../D2/commits/C5/cdk/package.json | 15 + .../D2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C5/commit.json | 20 + .../versionized_gt/D2/commits/C5/diff.patch | 16 + .../versionized_gt/D2/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/D2/commits/C6/cdk/cdk.ts | 465 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../D2/commits/C6/cdk/package.json | 15 + .../D2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/D2/commits/C6/commit.json | 20 + .../versionized_gt/D2/commits/C6/diff.patch | 15 + .../versionized_gt/D2/commits/provenance.json | 92 +++ .../versionized_gt/D2/metadata.yaml | 120 +++ .../versionized_gt/D2/repository | 1 + .../versionized_gt/D2/synth_context.json | 1 + .../versionized_gt/D2/template_analysis.json | 294 +++++++ .../versionized_gt/D3/README.md | 162 ++++ .../versionized_gt/D3/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C1/cdk/package.json | 15 + .../D3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C1/commit.json | 26 + .../versionized_gt/D3/commits/C1/diff.patch | 0 .../D3/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C10/cdk/cdk.ts | 469 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C10/cdk/package.json | 15 + .../D3/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C10/commit.json | 20 + .../versionized_gt/D3/commits/C10/diff.patch | 13 + .../D3/commits/C11/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C11/cdk/cdk.ts | 469 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C11/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C11/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C11/cdk/package.json | 15 + .../D3/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C11/commit.json | 20 + .../versionized_gt/D3/commits/C11/diff.patch | 11 + .../D3/commits/C12/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C12/cdk/cdk.ts | 469 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C12/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C12/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C12/cdk/package.json | 15 + .../D3/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C12/commit.json | 20 + .../versionized_gt/D3/commits/C12/diff.patch | 13 + .../D3/commits/C13/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C13/cdk/cdk.ts | 469 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C13/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C13/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C13/cdk/package.json | 15 + .../D3/commits/C13/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C13/commit.json | 20 + .../versionized_gt/D3/commits/C13/diff.patch | 12 + .../versionized_gt/D3/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C2/cdk/cdk.ts | 435 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C2/cdk/package.json | 15 + .../D3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C2/commit.json | 20 + .../versionized_gt/D3/commits/C2/diff.patch | 32 + .../versionized_gt/D3/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C3/cdk/cdk.ts | 446 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C3/cdk/package.json | 15 + .../D3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C3/commit.json | 20 + .../versionized_gt/D3/commits/C3/diff.patch | 22 + .../versionized_gt/D3/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C4/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C4/cdk/package.json | 15 + .../D3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C4/commit.json | 20 + .../versionized_gt/D3/commits/C4/diff.patch | 15 + .../versionized_gt/D3/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C5/cdk/cdk.ts | 455 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C5/cdk/package.json | 15 + .../D3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C5/commit.json | 20 + .../versionized_gt/D3/commits/C5/diff.patch | 16 + .../versionized_gt/D3/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C6/cdk/cdk.ts | 462 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C6/cdk/package.json | 15 + .../D3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C6/commit.json | 20 + .../versionized_gt/D3/commits/C6/diff.patch | 18 + .../versionized_gt/D3/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C7/cdk/cdk.ts | 462 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C7/cdk/package.json | 15 + .../D3/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C7/commit.json | 20 + .../versionized_gt/D3/commits/C7/diff.patch | 13 + .../versionized_gt/D3/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C8/cdk/cdk.ts | 468 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C8/cdk/package.json | 15 + .../D3/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C8/commit.json | 20 + .../versionized_gt/D3/commits/C8/diff.patch | 17 + .../versionized_gt/D3/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/D3/commits/C9/cdk/cdk.ts | 469 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../D3/commits/C9/cdk/package.json | 15 + .../D3/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/D3/commits/C9/commit.json | 20 + .../versionized_gt/D3/commits/C9/diff.patch | 11 + .../versionized_gt/D3/commits/provenance.json | 197 +++++ .../versionized_gt/D3/metadata.yaml | 188 +++++ .../versionized_gt/D3/repository | 1 + .../versionized_gt/D3/synth_context.json | 1 + .../versionized_gt/D3/template_analysis.json | 700 +++++++++++++++++ .../versionized_gt/D4/README.md | 137 ++++ .../versionized_gt/D4/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/D4/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../D4/commits/C1/cdk/package.json | 15 + .../D4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C1/commit.json | 26 + .../versionized_gt/D4/commits/C1/diff.patch | 0 .../versionized_gt/D4/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/D4/commits/C2/cdk/cdk.ts | 426 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../D4/commits/C2/cdk/package.json | 15 + .../D4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C2/commit.json | 20 + .../versionized_gt/D4/commits/C2/diff.patch | 23 + .../versionized_gt/D4/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/D4/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../D4/commits/C3/cdk/package.json | 15 + .../D4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C3/commit.json | 20 + .../versionized_gt/D4/commits/C3/diff.patch | 22 + .../versionized_gt/D4/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/D4/commits/C4/cdk/cdk.ts | 444 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../D4/commits/C4/cdk/package.json | 15 + .../D4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C4/commit.json | 20 + .../versionized_gt/D4/commits/C4/diff.patch | 18 + .../versionized_gt/D4/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/D4/commits/C5/cdk/cdk.ts | 449 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../D4/commits/C5/cdk/package.json | 15 + .../D4/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/D4/commits/C5/commit.json | 20 + .../versionized_gt/D4/commits/C5/diff.patch | 16 + .../versionized_gt/D4/commits/provenance.json | 77 ++ .../versionized_gt/D4/metadata.yaml | 106 +++ .../versionized_gt/D4/repository | 1 + .../versionized_gt/D4/synth_context.json | 1 + .../versionized_gt/D4/template_analysis.json | 248 ++++++ .../versionized_gt/E1/README.md | 136 ++++ .../versionized_gt/E1/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/E1/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../E1/commits/C1/cdk/package.json | 15 + .../E1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E1/commits/C1/commit.json | 26 + .../versionized_gt/E1/commits/C1/diff.patch | 0 .../versionized_gt/E1/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/E1/commits/C2/cdk/cdk.ts | 424 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../E1/commits/C2/cdk/package.json | 15 + .../E1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E1/commits/C2/commit.json | 20 + .../versionized_gt/E1/commits/C2/diff.patch | 18 + .../versionized_gt/E1/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/E1/commits/C3/cdk/cdk.ts | 435 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../E1/commits/C3/cdk/package.json | 15 + .../E1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E1/commits/C3/commit.json | 20 + .../versionized_gt/E1/commits/C3/diff.patch | 22 + .../versionized_gt/E1/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/E1/commits/C4/cdk/cdk.ts | 435 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../E1/commits/C4/cdk/package.json | 15 + .../E1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E1/commits/C4/commit.json | 20 + .../versionized_gt/E1/commits/C4/diff.patch | 16 + .../versionized_gt/E1/commits/provenance.json | 62 ++ .../versionized_gt/E1/metadata.yaml | 96 +++ .../versionized_gt/E1/repository | 1 + .../versionized_gt/E1/synth_context.json | 1 + .../versionized_gt/E1/template_analysis.json | 198 +++++ .../versionized_gt/E2/README.md | 144 ++++ .../versionized_gt/E2/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/E2/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../E2/commits/C1/cdk/package.json | 15 + .../E2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C1/commit.json | 26 + .../versionized_gt/E2/commits/C1/diff.patch | 0 .../versionized_gt/E2/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/E2/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../E2/commits/C2/cdk/package.json | 15 + .../E2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C2/commit.json | 20 + .../versionized_gt/E2/commits/C2/diff.patch | 22 + .../versionized_gt/E2/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/E2/commits/C3/cdk/cdk.ts | 431 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../E2/commits/C3/cdk/package.json | 15 + .../E2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C3/commit.json | 20 + .../versionized_gt/E2/commits/C3/diff.patch | 12 + .../versionized_gt/E2/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/E2/commits/C4/cdk/cdk.ts | 436 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../E2/commits/C4/cdk/package.json | 15 + .../E2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C4/commit.json | 20 + .../versionized_gt/E2/commits/C4/diff.patch | 16 + .../versionized_gt/E2/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/E2/commits/C5/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../E2/commits/C5/cdk/package.json | 15 + .../E2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C5/commit.json | 20 + .../versionized_gt/E2/commits/C5/diff.patch | 18 + .../versionized_gt/E2/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/E2/commits/C6/cdk/cdk.ts | 444 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../E2/commits/C6/cdk/package.json | 15 + .../E2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C6/commit.json | 20 + .../versionized_gt/E2/commits/C6/diff.patch | 12 + .../versionized_gt/E2/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/E2/commits/C7/cdk/cdk.ts | 444 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../E2/commits/C7/cdk/package.json | 15 + .../E2/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/E2/commits/C7/commit.json | 20 + .../versionized_gt/E2/commits/C7/diff.patch | 13 + .../versionized_gt/E2/commits/provenance.json | 107 +++ .../versionized_gt/E2/metadata.yaml | 120 +++ .../versionized_gt/E2/repository | 1 + .../versionized_gt/E2/synth_context.json | 1 + .../versionized_gt/E2/template_analysis.json | 344 ++++++++ .../versionized_gt/E3/README.md | 162 ++++ .../versionized_gt/E3/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C1/cdk/package.json | 15 + .../E3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C1/commit.json | 26 + .../versionized_gt/E3/commits/C1/diff.patch | 0 .../E3/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C10/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C10/cdk/package.json | 15 + .../E3/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C10/commit.json | 20 + .../versionized_gt/E3/commits/C10/diff.patch | 11 + .../E3/commits/C11/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C11/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C11/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C11/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C11/cdk/package.json | 15 + .../E3/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C11/commit.json | 20 + .../versionized_gt/E3/commits/C11/diff.patch | 12 + .../E3/commits/C12/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C12/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C12/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C12/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C12/cdk/package.json | 15 + .../E3/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C12/commit.json | 20 + .../versionized_gt/E3/commits/C12/diff.patch | 15 + .../E3/commits/C13/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C13/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C13/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C13/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C13/cdk/package.json | 15 + .../E3/commits/C13/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C13/commit.json | 20 + .../versionized_gt/E3/commits/C13/diff.patch | 13 + .../versionized_gt/E3/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C2/cdk/package.json | 15 + .../E3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C2/commit.json | 20 + .../versionized_gt/E3/commits/C2/diff.patch | 22 + .../versionized_gt/E3/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C3/cdk/package.json | 15 + .../E3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C3/commit.json | 20 + .../versionized_gt/E3/commits/C3/diff.patch | 18 + .../versionized_gt/E3/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C4/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C4/cdk/package.json | 15 + .../E3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C4/commit.json | 20 + .../versionized_gt/E3/commits/C4/diff.patch | 11 + .../versionized_gt/E3/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C5/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C5/cdk/package.json | 15 + .../E3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C5/commit.json | 20 + .../versionized_gt/E3/commits/C5/diff.patch | 13 + .../versionized_gt/E3/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C6/cdk/cdk.ts | 440 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C6/cdk/package.json | 15 + .../E3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C6/commit.json | 20 + .../versionized_gt/E3/commits/C6/diff.patch | 13 + .../versionized_gt/E3/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C7/cdk/cdk.ts | 445 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C7/cdk/package.json | 15 + .../E3/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C7/commit.json | 20 + .../versionized_gt/E3/commits/C7/diff.patch | 16 + .../versionized_gt/E3/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C8/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C8/cdk/package.json | 15 + .../E3/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C8/commit.json | 20 + .../versionized_gt/E3/commits/C8/diff.patch | 17 + .../versionized_gt/E3/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/E3/commits/C9/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../E3/commits/C9/cdk/package.json | 15 + .../E3/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/E3/commits/C9/commit.json | 20 + .../versionized_gt/E3/commits/C9/diff.patch | 13 + .../versionized_gt/E3/commits/provenance.json | 197 +++++ .../versionized_gt/E3/metadata.yaml | 184 +++++ .../versionized_gt/E3/repository | 1 + .../versionized_gt/E3/synth_context.json | 1 + .../versionized_gt/E3/template_analysis.json | 632 +++++++++++++++ .../versionized_gt/E4/README.md | 141 ++++ .../versionized_gt/E4/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/E4/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../E4/commits/C1/cdk/package.json | 15 + .../E4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C1/commit.json | 26 + .../versionized_gt/E4/commits/C1/diff.patch | 0 .../versionized_gt/E4/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/E4/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../E4/commits/C2/cdk/package.json | 15 + .../E4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C2/commit.json | 20 + .../versionized_gt/E4/commits/C2/diff.patch | 22 + .../versionized_gt/E4/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/E4/commits/C3/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../E4/commits/C3/cdk/package.json | 15 + .../E4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C3/commit.json | 20 + .../versionized_gt/E4/commits/C3/diff.patch | 19 + .../versionized_gt/E4/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/E4/commits/C4/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../E4/commits/C4/cdk/package.json | 15 + .../E4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C4/commit.json | 20 + .../versionized_gt/E4/commits/C4/diff.patch | 16 + .../versionized_gt/E4/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/E4/commits/C5/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../E4/commits/C5/cdk/package.json | 15 + .../E4/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C5/commit.json | 20 + .../versionized_gt/E4/commits/C5/diff.patch | 19 + .../versionized_gt/E4/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/E4/commits/C6/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../E4/commits/C6/cdk/package.json | 15 + .../E4/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/E4/commits/C6/commit.json | 20 + .../versionized_gt/E4/commits/C6/diff.patch | 13 + .../versionized_gt/E4/commits/provenance.json | 92 +++ .../versionized_gt/E4/metadata.yaml | 116 +++ .../versionized_gt/E4/repository | 1 + .../versionized_gt/E4/synth_context.json | 1 + .../versionized_gt/E4/template_analysis.json | 294 +++++++ .../versionized_gt/E5/README.md | 138 ++++ .../versionized_gt/E5/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/E5/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../E5/commits/C1/cdk/package.json | 15 + .../E5/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/E5/commits/C1/commit.json | 26 + .../versionized_gt/E5/commits/C1/diff.patch | 0 .../versionized_gt/E5/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/E5/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../E5/commits/C2/cdk/package.json | 15 + .../E5/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/E5/commits/C2/commit.json | 20 + .../versionized_gt/E5/commits/C2/diff.patch | 22 + .../versionized_gt/E5/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/E5/commits/C3/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../E5/commits/C3/cdk/package.json | 15 + .../E5/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/E5/commits/C3/commit.json | 20 + .../versionized_gt/E5/commits/C3/diff.patch | 19 + .../versionized_gt/E5/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/E5/commits/C4/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../E5/commits/C4/cdk/package.json | 15 + .../E5/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/E5/commits/C4/commit.json | 20 + .../versionized_gt/E5/commits/C4/diff.patch | 16 + .../versionized_gt/E5/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/E5/commits/C5/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../E5/commits/C5/cdk/package.json | 15 + .../E5/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/E5/commits/C5/commit.json | 20 + .../versionized_gt/E5/commits/C5/diff.patch | 17 + .../versionized_gt/E5/commits/provenance.json | 77 ++ .../versionized_gt/E5/metadata.yaml | 103 +++ .../versionized_gt/E5/repository | 1 + .../versionized_gt/E5/synth_context.json | 1 + .../versionized_gt/E5/template_analysis.json | 246 ++++++ .../versionized_gt/G1/README.md | 150 ++++ .../versionized_gt/G1/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C1/cdk/package.json | 15 + .../G1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C1/commit.json | 26 + .../versionized_gt/G1/commits/C1/diff.patch | 0 .../versionized_gt/G1/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C2/cdk/cdk.ts | 425 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C2/cdk/package.json | 15 + .../G1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C2/commit.json | 20 + .../versionized_gt/G1/commits/C2/diff.patch | 17 + .../versionized_gt/G1/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C3/cdk/cdk.ts | 436 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C3/cdk/package.json | 15 + .../G1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C3/commit.json | 20 + .../versionized_gt/G1/commits/C3/diff.patch | 22 + .../versionized_gt/G1/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C4/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C4/cdk/package.json | 15 + .../G1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C4/commit.json | 20 + .../versionized_gt/G1/commits/C4/diff.patch | 18 + .../versionized_gt/G1/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C5/cdk/cdk.ts | 444 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C5/cdk/package.json | 15 + .../G1/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C5/commit.json | 20 + .../versionized_gt/G1/commits/C5/diff.patch | 14 + .../versionized_gt/G1/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C6/cdk/cdk.ts | 449 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C6/cdk/package.json | 15 + .../G1/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C6/commit.json | 20 + .../versionized_gt/G1/commits/C6/diff.patch | 16 + .../versionized_gt/G1/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C7/cdk/cdk.ts | 449 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C7/cdk/package.json | 15 + .../G1/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C7/commit.json | 20 + .../versionized_gt/G1/commits/C7/diff.patch | 13 + .../versionized_gt/G1/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C8/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C8/cdk/package.json | 15 + .../G1/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C8/commit.json | 20 + .../versionized_gt/G1/commits/C8/diff.patch | 18 + .../versionized_gt/G1/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/G1/commits/C9/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../G1/commits/C9/cdk/package.json | 15 + .../G1/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/G1/commits/C9/commit.json | 20 + .../versionized_gt/G1/commits/C9/diff.patch | 13 + .../versionized_gt/G1/commits/provenance.json | 137 ++++ .../versionized_gt/G1/metadata.yaml | 144 ++++ .../versionized_gt/G1/repository | 1 + .../versionized_gt/G1/synth_context.json | 1 + .../versionized_gt/G1/template_analysis.json | 446 +++++++++++ .../versionized_gt/G2/README.md | 138 ++++ .../versionized_gt/G2/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/G2/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../G2/commits/C1/cdk/package.json | 15 + .../G2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C1/commit.json | 26 + .../versionized_gt/G2/commits/C1/diff.patch | 0 .../versionized_gt/G2/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/G2/commits/C2/cdk/cdk.ts | 429 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../G2/commits/C2/cdk/package.json | 15 + .../G2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C2/commit.json | 20 + .../versionized_gt/G2/commits/C2/diff.patch | 21 + .../versionized_gt/G2/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/G2/commits/C3/cdk/cdk.ts | 429 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../G2/commits/C3/cdk/package.json | 15 + .../G2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C3/commit.json | 20 + .../versionized_gt/G2/commits/C3/diff.patch | 13 + .../versionized_gt/G2/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/G2/commits/C4/cdk/cdk.ts | 440 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../G2/commits/C4/cdk/package.json | 15 + .../G2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C4/commit.json | 20 + .../versionized_gt/G2/commits/C4/diff.patch | 22 + .../versionized_gt/G2/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/G2/commits/C5/cdk/cdk.ts | 440 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../G2/commits/C5/cdk/package.json | 15 + .../G2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G2/commits/C5/commit.json | 20 + .../versionized_gt/G2/commits/C5/diff.patch | 13 + .../versionized_gt/G2/commits/provenance.json | 77 ++ .../versionized_gt/G2/metadata.yaml | 103 +++ .../versionized_gt/G2/repository | 1 + .../versionized_gt/G2/synth_context.json | 1 + .../versionized_gt/G2/template_analysis.json | 248 ++++++ .../versionized_gt/G3/README.md | 165 ++++ .../versionized_gt/G3/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C1/cdk/package.json | 15 + .../G3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C1/commit.json | 26 + .../versionized_gt/G3/commits/C1/diff.patch | 0 .../G3/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C10/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C10/cdk/package.json | 15 + .../G3/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C10/commit.json | 20 + .../versionized_gt/G3/commits/C10/diff.patch | 11 + .../G3/commits/C11/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C11/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C11/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C11/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C11/cdk/package.json | 15 + .../G3/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C11/commit.json | 20 + .../versionized_gt/G3/commits/C11/diff.patch | 12 + .../G3/commits/C12/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C12/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C12/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C12/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C12/cdk/package.json | 15 + .../G3/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C12/commit.json | 20 + .../versionized_gt/G3/commits/C12/diff.patch | 13 + .../G3/commits/C13/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C13/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C13/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C13/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C13/cdk/package.json | 15 + .../G3/commits/C13/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C13/commit.json | 20 + .../versionized_gt/G3/commits/C13/diff.patch | 13 + .../G3/commits/C14/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C14/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C14/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C14/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C14/cdk/package.json | 15 + .../G3/commits/C14/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C14/commit.json | 20 + .../versionized_gt/G3/commits/C14/diff.patch | 12 + .../versionized_gt/G3/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C2/cdk/package.json | 15 + .../G3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C2/commit.json | 20 + .../versionized_gt/G3/commits/C2/diff.patch | 22 + .../versionized_gt/G3/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C3/cdk/package.json | 15 + .../G3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C3/commit.json | 20 + .../versionized_gt/G3/commits/C3/diff.patch | 18 + .../versionized_gt/G3/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C4/cdk/cdk.ts | 447 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C4/cdk/package.json | 15 + .../G3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C4/commit.json | 20 + .../versionized_gt/G3/commits/C4/diff.patch | 21 + .../versionized_gt/G3/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C5/cdk/cdk.ts | 452 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C5/cdk/package.json | 15 + .../G3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C5/commit.json | 20 + .../versionized_gt/G3/commits/C5/diff.patch | 16 + .../versionized_gt/G3/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C6/cdk/cdk.ts | 452 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C6/cdk/package.json | 15 + .../G3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C6/commit.json | 20 + .../versionized_gt/G3/commits/C6/diff.patch | 13 + .../versionized_gt/G3/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C7/cdk/cdk.ts | 458 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C7/cdk/package.json | 15 + .../G3/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C7/commit.json | 20 + .../versionized_gt/G3/commits/C7/diff.patch | 17 + .../versionized_gt/G3/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C8/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C8/cdk/package.json | 15 + .../G3/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C8/commit.json | 20 + .../versionized_gt/G3/commits/C8/diff.patch | 11 + .../versionized_gt/G3/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/G3/commits/C9/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../G3/commits/C9/cdk/package.json | 15 + .../G3/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/G3/commits/C9/commit.json | 20 + .../versionized_gt/G3/commits/C9/diff.patch | 13 + .../versionized_gt/G3/commits/provenance.json | 212 +++++ .../versionized_gt/G3/metadata.yaml | 195 +++++ .../versionized_gt/G3/repository | 1 + .../versionized_gt/G3/synth_context.json | 1 + .../versionized_gt/G3/template_analysis.json | 704 +++++++++++++++++ .../versionized_gt/G4/README.md | 135 ++++ .../versionized_gt/G4/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/G4/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../G4/commits/C1/cdk/package.json | 15 + .../G4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C1/commit.json | 26 + .../versionized_gt/G4/commits/C1/diff.patch | 0 .../versionized_gt/G4/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/G4/commits/C2/cdk/cdk.ts | 425 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../G4/commits/C2/cdk/package.json | 15 + .../G4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C2/commit.json | 20 + .../versionized_gt/G4/commits/C2/diff.patch | 17 + .../versionized_gt/G4/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/G4/commits/C3/cdk/cdk.ts | 436 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../G4/commits/C3/cdk/package.json | 15 + .../G4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C3/commit.json | 20 + .../versionized_gt/G4/commits/C3/diff.patch | 22 + .../versionized_gt/G4/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/G4/commits/C4/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../G4/commits/C4/cdk/package.json | 15 + .../G4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G4/commits/C4/commit.json | 20 + .../versionized_gt/G4/commits/C4/diff.patch | 17 + .../versionized_gt/G4/commits/provenance.json | 62 ++ .../versionized_gt/G4/metadata.yaml | 97 +++ .../versionized_gt/G4/repository | 1 + .../versionized_gt/G4/synth_context.json | 1 + .../versionized_gt/G4/template_analysis.json | 201 +++++ .../versionized_gt/G5/README.md | 150 ++++ .../versionized_gt/G5/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C1/cdk/package.json | 15 + .../G5/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C1/commit.json | 26 + .../versionized_gt/G5/commits/C1/diff.patch | 0 .../versionized_gt/G5/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C2/cdk/package.json | 15 + .../G5/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C2/commit.json | 20 + .../versionized_gt/G5/commits/C2/diff.patch | 22 + .../versionized_gt/G5/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C3/cdk/package.json | 15 + .../G5/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C3/commit.json | 20 + .../versionized_gt/G5/commits/C3/diff.patch | 18 + .../versionized_gt/G5/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C4/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C4/cdk/package.json | 15 + .../G5/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C4/commit.json | 20 + .../versionized_gt/G5/commits/C4/diff.patch | 13 + .../versionized_gt/G5/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C5/cdk/cdk.ts | 447 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C5/cdk/package.json | 15 + .../G5/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C5/commit.json | 20 + .../versionized_gt/G5/commits/C5/diff.patch | 21 + .../versionized_gt/G5/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C6/cdk/cdk.ts | 452 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C6/cdk/package.json | 15 + .../G5/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C6/commit.json | 20 + .../versionized_gt/G5/commits/C6/diff.patch | 16 + .../versionized_gt/G5/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C7/cdk/cdk.ts | 452 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C7/cdk/package.json | 15 + .../G5/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C7/commit.json | 20 + .../versionized_gt/G5/commits/C7/diff.patch | 13 + .../versionized_gt/G5/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C8/cdk/cdk.ts | 452 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C8/cdk/package.json | 15 + .../G5/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C8/commit.json | 20 + .../versionized_gt/G5/commits/C8/diff.patch | 11 + .../versionized_gt/G5/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/G5/commits/C9/cdk/cdk.ts | 463 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../G5/commits/C9/cdk/package.json | 15 + .../G5/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/G5/commits/C9/commit.json | 20 + .../versionized_gt/G5/commits/C9/diff.patch | 22 + .../versionized_gt/G5/commits/provenance.json | 137 ++++ .../versionized_gt/G5/metadata.yaml | 143 ++++ .../versionized_gt/G5/repository | 1 + .../versionized_gt/G5/synth_context.json | 1 + .../versionized_gt/G5/template_analysis.json | 438 +++++++++++ .../versionized_gt/G6/README.md | 141 ++++ .../versionized_gt/G6/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/G6/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../G6/commits/C1/cdk/package.json | 15 + .../G6/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G6/commits/C1/commit.json | 26 + .../versionized_gt/G6/commits/C1/diff.patch | 0 .../versionized_gt/G6/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/G6/commits/C2/cdk/cdk.ts | 429 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../G6/commits/C2/cdk/package.json | 15 + .../G6/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G6/commits/C2/commit.json | 20 + .../versionized_gt/G6/commits/C2/diff.patch | 21 + .../versionized_gt/G6/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/G6/commits/C3/cdk/cdk.ts | 429 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../G6/commits/C3/cdk/package.json | 15 + .../G6/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G6/commits/C3/commit.json | 20 + .../versionized_gt/G6/commits/C3/diff.patch | 13 + .../versionized_gt/G6/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/G6/commits/C4/cdk/cdk.ts | 440 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../G6/commits/C4/cdk/package.json | 15 + .../G6/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G6/commits/C4/commit.json | 20 + .../versionized_gt/G6/commits/C4/diff.patch | 22 + .../versionized_gt/G6/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/G6/commits/C5/cdk/cdk.ts | 445 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../G6/commits/C5/cdk/package.json | 15 + .../G6/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G6/commits/C5/commit.json | 20 + .../versionized_gt/G6/commits/C5/diff.patch | 16 + .../versionized_gt/G6/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/G6/commits/C6/cdk/cdk.ts | 452 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../G6/commits/C6/cdk/package.json | 15 + .../G6/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/G6/commits/C6/commit.json | 20 + .../versionized_gt/G6/commits/C6/diff.patch | 18 + .../versionized_gt/G6/commits/provenance.json | 92 +++ .../versionized_gt/G6/metadata.yaml | 115 +++ .../versionized_gt/G6/repository | 1 + .../versionized_gt/G6/synth_context.json | 1 + .../versionized_gt/G6/template_analysis.json | 298 +++++++ .../versionized_gt/G7/README.md | 168 ++++ .../versionized_gt/G7/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C1/cdk/package.json | 15 + .../G7/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C1/commit.json | 26 + .../versionized_gt/G7/commits/C1/diff.patch | 0 .../G7/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C10/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C10/cdk/package.json | 15 + .../G7/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C10/commit.json | 20 + .../versionized_gt/G7/commits/C10/diff.patch | 11 + .../G7/commits/C11/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C11/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C11/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C11/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C11/cdk/package.json | 15 + .../G7/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C11/commit.json | 20 + .../versionized_gt/G7/commits/C11/diff.patch | 12 + .../G7/commits/C12/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C12/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C12/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C12/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C12/cdk/package.json | 15 + .../G7/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C12/commit.json | 20 + .../versionized_gt/G7/commits/C12/diff.patch | 13 + .../G7/commits/C13/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C13/cdk/cdk.ts | 466 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C13/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C13/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C13/cdk/package.json | 15 + .../G7/commits/C13/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C13/commit.json | 20 + .../versionized_gt/G7/commits/C13/diff.patch | 18 + .../G7/commits/C14/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C14/cdk/cdk.ts | 466 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C14/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C14/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C14/cdk/package.json | 15 + .../G7/commits/C14/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C14/commit.json | 20 + .../versionized_gt/G7/commits/C14/diff.patch | 12 + .../G7/commits/C15/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C15/cdk/cdk.ts | 466 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C15/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C15/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C15/cdk/package.json | 15 + .../G7/commits/C15/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C15/commit.json | 20 + .../versionized_gt/G7/commits/C15/diff.patch | 13 + .../versionized_gt/G7/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C2/cdk/package.json | 15 + .../G7/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C2/commit.json | 20 + .../versionized_gt/G7/commits/C2/diff.patch | 22 + .../versionized_gt/G7/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C3/cdk/package.json | 15 + .../G7/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C3/commit.json | 20 + .../versionized_gt/G7/commits/C3/diff.patch | 18 + .../versionized_gt/G7/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C4/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C4/cdk/package.json | 15 + .../G7/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C4/commit.json | 20 + .../versionized_gt/G7/commits/C4/diff.patch | 11 + .../versionized_gt/G7/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C5/cdk/cdk.ts | 448 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C5/cdk/package.json | 15 + .../G7/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C5/commit.json | 20 + .../versionized_gt/G7/commits/C5/diff.patch | 21 + .../versionized_gt/G7/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C6/cdk/cdk.ts | 453 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C6/cdk/package.json | 15 + .../G7/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C6/commit.json | 20 + .../versionized_gt/G7/commits/C6/diff.patch | 16 + .../versionized_gt/G7/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C7/cdk/cdk.ts | 453 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C7/cdk/package.json | 15 + .../G7/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C7/commit.json | 20 + .../versionized_gt/G7/commits/C7/diff.patch | 13 + .../versionized_gt/G7/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C8/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C8/cdk/package.json | 15 + .../G7/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C8/commit.json | 20 + .../versionized_gt/G7/commits/C8/diff.patch | 17 + .../versionized_gt/G7/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/G7/commits/C9/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../G7/commits/C9/cdk/package.json | 15 + .../G7/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/G7/commits/C9/commit.json | 20 + .../versionized_gt/G7/commits/C9/diff.patch | 13 + .../versionized_gt/G7/commits/provenance.json | 227 ++++++ .../versionized_gt/G7/metadata.yaml | 203 +++++ .../versionized_gt/G7/repository | 1 + .../versionized_gt/G7/synth_context.json | 1 + .../versionized_gt/G7/template_analysis.json | 738 ++++++++++++++++++ .../versionized_gt/PULL_REQUEST.md | 153 ++++ .../versionized_gt/R1/README.md | 151 ++++ .../versionized_gt/R1/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C1/cdk/package.json | 15 + .../R1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C1/commit.json | 26 + .../versionized_gt/R1/commits/C1/diff.patch | 0 .../versionized_gt/R1/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C2/cdk/package.json | 15 + .../R1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C2/commit.json | 20 + .../versionized_gt/R1/commits/C2/diff.patch | 22 + .../versionized_gt/R1/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C3/cdk/package.json | 15 + .../R1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C3/commit.json | 20 + .../versionized_gt/R1/commits/C3/diff.patch | 18 + .../versionized_gt/R1/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C4/cdk/cdk.ts | 445 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C4/cdk/package.json | 15 + .../R1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C4/commit.json | 20 + .../versionized_gt/R1/commits/C4/diff.patch | 19 + .../versionized_gt/R1/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C5/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C5/cdk/package.json | 15 + .../R1/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C5/commit.json | 20 + .../versionized_gt/R1/commits/C5/diff.patch | 16 + .../versionized_gt/R1/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C6/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C6/cdk/package.json | 15 + .../R1/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C6/commit.json | 20 + .../versionized_gt/R1/commits/C6/diff.patch | 19 + .../versionized_gt/R1/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C7/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C7/cdk/package.json | 15 + .../R1/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C7/commit.json | 20 + .../versionized_gt/R1/commits/C7/diff.patch | 13 + .../versionized_gt/R1/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C8/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C8/cdk/package.json | 15 + .../R1/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C8/commit.json | 20 + .../versionized_gt/R1/commits/C8/diff.patch | 11 + .../versionized_gt/R1/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/R1/commits/C9/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../R1/commits/C9/cdk/package.json | 15 + .../R1/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/R1/commits/C9/commit.json | 20 + .../versionized_gt/R1/commits/C9/diff.patch | 13 + .../versionized_gt/R1/commits/provenance.json | 137 ++++ .../versionized_gt/R1/metadata.yaml | 152 ++++ .../versionized_gt/R1/repository | 1 + .../versionized_gt/R1/synth_context.json | 1 + .../versionized_gt/R1/template_analysis.json | 480 ++++++++++++ .../versionized_gt/R2/README.md | 157 ++++ .../versionized_gt/R2/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C1/cdk/package.json | 15 + .../R2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C1/commit.json | 26 + .../versionized_gt/R2/commits/C1/diff.patch | 0 .../R2/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C10/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C10/cdk/package.json | 15 + .../R2/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C10/commit.json | 20 + .../versionized_gt/R2/commits/C10/diff.patch | 15 + .../R2/commits/C11/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C11/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C11/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C11/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C11/cdk/package.json | 15 + .../R2/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C11/commit.json | 20 + .../versionized_gt/R2/commits/C11/diff.patch | 12 + .../versionized_gt/R2/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C2/cdk/package.json | 15 + .../R2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C2/commit.json | 20 + .../versionized_gt/R2/commits/C2/diff.patch | 22 + .../versionized_gt/R2/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C3/cdk/cdk.ts | 431 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C3/cdk/package.json | 15 + .../R2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C3/commit.json | 20 + .../versionized_gt/R2/commits/C3/diff.patch | 11 + .../versionized_gt/R2/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C4/cdk/cdk.ts | 433 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C4/cdk/package.json | 15 + .../R2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C4/commit.json | 20 + .../versionized_gt/R2/commits/C4/diff.patch | 22 + .../versionized_gt/R2/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C5/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C5/cdk/package.json | 15 + .../R2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C5/commit.json | 20 + .../versionized_gt/R2/commits/C5/diff.patch | 16 + .../versionized_gt/R2/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C6/cdk/cdk.ts | 445 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C6/cdk/package.json | 15 + .../R2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C6/commit.json | 20 + .../versionized_gt/R2/commits/C6/diff.patch | 18 + .../versionized_gt/R2/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C7/cdk/cdk.ts | 445 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C7/cdk/package.json | 15 + .../R2/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C7/commit.json | 20 + .../versionized_gt/R2/commits/C7/diff.patch | 13 + .../versionized_gt/R2/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C8/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C8/cdk/package.json | 15 + .../R2/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C8/commit.json | 20 + .../versionized_gt/R2/commits/C8/diff.patch | 17 + .../versionized_gt/R2/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/R2/commits/C9/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../R2/commits/C9/cdk/package.json | 15 + .../R2/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/R2/commits/C9/commit.json | 20 + .../versionized_gt/R2/commits/C9/diff.patch | 13 + .../versionized_gt/R2/commits/provenance.json | 167 ++++ .../versionized_gt/R2/metadata.yaml | 171 ++++ .../versionized_gt/R2/repository | 1 + .../versionized_gt/R2/synth_context.json | 1 + .../versionized_gt/R2/template_analysis.json | 536 +++++++++++++ .../versionized_gt/R3/README.md | 141 ++++ .../versionized_gt/R3/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/R3/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../R3/commits/C1/cdk/package.json | 15 + .../R3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/R3/commits/C1/commit.json | 26 + .../versionized_gt/R3/commits/C1/diff.patch | 0 .../versionized_gt/R3/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/R3/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../R3/commits/C2/cdk/package.json | 15 + .../R3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/R3/commits/C2/commit.json | 20 + .../versionized_gt/R3/commits/C2/diff.patch | 22 + .../versionized_gt/R3/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/R3/commits/C3/cdk/cdk.ts | 432 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../R3/commits/C3/cdk/package.json | 15 + .../R3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/R3/commits/C3/commit.json | 20 + .../versionized_gt/R3/commits/C3/diff.patch | 13 + .../versionized_gt/R3/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/R3/commits/C4/cdk/cdk.ts | 432 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../R3/commits/C4/cdk/package.json | 15 + .../R3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/R3/commits/C4/commit.json | 20 + .../versionized_gt/R3/commits/C4/diff.patch | 15 + .../versionized_gt/R3/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/R3/commits/C5/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../R3/commits/C5/cdk/package.json | 15 + .../R3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/R3/commits/C5/commit.json | 20 + .../versionized_gt/R3/commits/C5/diff.patch | 16 + .../versionized_gt/R3/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/R3/commits/C6/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../R3/commits/C6/cdk/package.json | 15 + .../R3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/R3/commits/C6/commit.json | 20 + .../versionized_gt/R3/commits/C6/diff.patch | 13 + .../versionized_gt/R3/commits/provenance.json | 92 +++ .../versionized_gt/R3/metadata.yaml | 116 +++ .../versionized_gt/R3/repository | 1 + .../versionized_gt/R3/synth_context.json | 1 + .../versionized_gt/R3/template_analysis.json | 294 +++++++ .../versionized_gt/R4/README.md | 148 ++++ .../versionized_gt/R4/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/R4/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../R4/commits/C1/cdk/package.json | 15 + .../R4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/R4/commits/C1/commit.json | 26 + .../versionized_gt/R4/commits/C1/diff.patch | 0 .../versionized_gt/R4/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/R4/commits/C2/cdk/cdk.ts | 426 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../R4/commits/C2/cdk/package.json | 15 + .../R4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/R4/commits/C2/commit.json | 20 + .../versionized_gt/R4/commits/C2/diff.patch | 23 + .../versionized_gt/R4/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/R4/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../R4/commits/C3/cdk/package.json | 15 + .../R4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/R4/commits/C3/commit.json | 20 + .../versionized_gt/R4/commits/C3/diff.patch | 22 + .../versionized_gt/R4/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/R4/commits/C4/cdk/cdk.ts | 445 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../R4/commits/C4/cdk/package.json | 15 + .../R4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/R4/commits/C4/commit.json | 20 + .../versionized_gt/R4/commits/C4/diff.patch | 19 + .../versionized_gt/R4/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/R4/commits/C5/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../R4/commits/C5/cdk/package.json | 15 + .../R4/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/R4/commits/C5/commit.json | 20 + .../versionized_gt/R4/commits/C5/diff.patch | 16 + .../versionized_gt/R4/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/R4/commits/C6/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../R4/commits/C6/cdk/package.json | 15 + .../R4/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/R4/commits/C6/commit.json | 20 + .../versionized_gt/R4/commits/C6/diff.patch | 17 + .../versionized_gt/R4/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/R4/commits/C7/cdk/cdk.ts | 455 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../R4/commits/C7/cdk/package.json | 15 + .../R4/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/R4/commits/C7/commit.json | 20 + .../versionized_gt/R4/commits/C7/diff.patch | 18 + .../versionized_gt/R4/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/R4/commits/C8/cdk/cdk.ts | 455 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../R4/commits/C8/cdk/package.json | 15 + .../R4/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/R4/commits/C8/commit.json | 20 + .../versionized_gt/R4/commits/C8/diff.patch | 11 + .../versionized_gt/R4/commits/provenance.json | 122 +++ .../versionized_gt/R4/metadata.yaml | 144 ++++ .../versionized_gt/R4/repository | 1 + .../versionized_gt/R4/synth_context.json | 1 + .../versionized_gt/R4/template_analysis.json | 425 ++++++++++ .../versionized_gt/R5/README.md | 163 ++++ .../versionized_gt/R5/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C1/cdk/package.json | 15 + .../R5/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C1/commit.json | 26 + .../versionized_gt/R5/commits/C1/diff.patch | 0 .../R5/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C10/cdk/cdk.ts | 471 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C10/cdk/package.json | 15 + .../R5/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C10/commit.json | 20 + .../versionized_gt/R5/commits/C10/diff.patch | 11 + .../R5/commits/C11/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C11/cdk/cdk.ts | 471 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C11/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C11/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C11/cdk/package.json | 15 + .../R5/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C11/commit.json | 20 + .../versionized_gt/R5/commits/C11/diff.patch | 12 + .../R5/commits/C12/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C12/cdk/cdk.ts | 471 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C12/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C12/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C12/cdk/package.json | 15 + .../R5/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C12/commit.json | 20 + .../versionized_gt/R5/commits/C12/diff.patch | 20 + .../R5/commits/C13/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C13/cdk/cdk.ts | 471 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C13/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C13/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C13/cdk/package.json | 15 + .../R5/commits/C13/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C13/commit.json | 20 + .../versionized_gt/R5/commits/C13/diff.patch | 13 + .../versionized_gt/R5/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C2/cdk/package.json | 15 + .../R5/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C2/commit.json | 20 + .../versionized_gt/R5/commits/C2/diff.patch | 22 + .../versionized_gt/R5/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C3/cdk/cdk.ts | 431 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C3/cdk/package.json | 15 + .../R5/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C3/commit.json | 20 + .../versionized_gt/R5/commits/C3/diff.patch | 11 + .../versionized_gt/R5/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C4/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C4/cdk/package.json | 15 + .../R5/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C4/commit.json | 20 + .../versionized_gt/R5/commits/C4/diff.patch | 18 + .../versionized_gt/R5/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C5/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C5/cdk/package.json | 15 + .../R5/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C5/commit.json | 20 + .../versionized_gt/R5/commits/C5/diff.patch | 13 + .../versionized_gt/R5/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C6/cdk/cdk.ts | 460 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C6/cdk/package.json | 15 + .../R5/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C6/commit.json | 20 + .../versionized_gt/R5/commits/C6/diff.patch | 33 + .../versionized_gt/R5/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C7/cdk/cdk.ts | 465 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C7/cdk/package.json | 15 + .../R5/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C7/commit.json | 20 + .../versionized_gt/R5/commits/C7/diff.patch | 16 + .../versionized_gt/R5/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C8/cdk/cdk.ts | 471 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C8/cdk/package.json | 15 + .../R5/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C8/commit.json | 20 + .../versionized_gt/R5/commits/C8/diff.patch | 17 + .../versionized_gt/R5/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/R5/commits/C9/cdk/cdk.ts | 471 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../R5/commits/C9/cdk/package.json | 15 + .../R5/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/R5/commits/C9/commit.json | 20 + .../versionized_gt/R5/commits/C9/diff.patch | 13 + .../versionized_gt/R5/commits/provenance.json | 197 +++++ .../versionized_gt/R5/metadata.yaml | 193 +++++ .../versionized_gt/R5/repository | 1 + .../versionized_gt/R5/synth_context.json | 1 + .../versionized_gt/R5/template_analysis.json | 686 ++++++++++++++++ .../versionized_gt/README.md | 132 ++++ .../versionized_gt/T1/README.md | 147 ++++ .../versionized_gt/T1/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/T1/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../T1/commits/C1/cdk/package.json | 15 + .../T1/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C1/commit.json | 26 + .../versionized_gt/T1/commits/C1/diff.patch | 0 .../versionized_gt/T1/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/T1/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../T1/commits/C2/cdk/package.json | 15 + .../T1/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C2/commit.json | 20 + .../versionized_gt/T1/commits/C2/diff.patch | 22 + .../versionized_gt/T1/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/T1/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../T1/commits/C3/cdk/package.json | 15 + .../T1/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C3/commit.json | 20 + .../versionized_gt/T1/commits/C3/diff.patch | 18 + .../versionized_gt/T1/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/T1/commits/C4/cdk/cdk.ts | 453 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../T1/commits/C4/cdk/package.json | 15 + .../T1/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C4/commit.json | 20 + .../versionized_gt/T1/commits/C4/diff.patch | 27 + .../versionized_gt/T1/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/T1/commits/C5/cdk/cdk.ts | 458 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../T1/commits/C5/cdk/package.json | 15 + .../T1/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C5/commit.json | 20 + .../versionized_gt/T1/commits/C5/diff.patch | 16 + .../versionized_gt/T1/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/T1/commits/C6/cdk/cdk.ts | 458 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../T1/commits/C6/cdk/package.json | 15 + .../T1/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C6/commit.json | 20 + .../versionized_gt/T1/commits/C6/diff.patch | 13 + .../versionized_gt/T1/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/T1/commits/C7/cdk/cdk.ts | 458 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../T1/commits/C7/cdk/package.json | 15 + .../T1/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C7/commit.json | 20 + .../versionized_gt/T1/commits/C7/diff.patch | 30 + .../versionized_gt/T1/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/T1/commits/C8/cdk/cdk.ts | 458 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../T1/commits/C8/cdk/package.json | 15 + .../T1/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T1/commits/C8/commit.json | 20 + .../versionized_gt/T1/commits/C8/diff.patch | 11 + .../versionized_gt/T1/commits/provenance.json | 122 +++ .../versionized_gt/T1/metadata.yaml | 136 ++++ .../versionized_gt/T1/repository | 1 + .../versionized_gt/T1/synth_context.json | 1 + .../versionized_gt/T1/template_analysis.json | 390 +++++++++ .../versionized_gt/T2/README.md | 150 ++++ .../versionized_gt/T2/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C1/cdk/package.json | 15 + .../T2/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C1/commit.json | 26 + .../versionized_gt/T2/commits/C1/diff.patch | 0 .../versionized_gt/T2/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C2/cdk/package.json | 15 + .../T2/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C2/commit.json | 20 + .../versionized_gt/T2/commits/C2/diff.patch | 22 + .../versionized_gt/T2/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C3/cdk/package.json | 15 + .../T2/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C3/commit.json | 20 + .../versionized_gt/T2/commits/C3/diff.patch | 18 + .../versionized_gt/T2/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C4/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C4/cdk/package.json | 15 + .../T2/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C4/commit.json | 20 + .../versionized_gt/T2/commits/C4/diff.patch | 17 + .../versionized_gt/T2/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C5/cdk/cdk.ts | 448 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C5/cdk/package.json | 15 + .../T2/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C5/commit.json | 20 + .../versionized_gt/T2/commits/C5/diff.patch | 16 + .../versionized_gt/T2/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C6/cdk/cdk.ts | 448 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C6/cdk/package.json | 15 + .../T2/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C6/commit.json | 20 + .../versionized_gt/T2/commits/C6/diff.patch | 13 + .../versionized_gt/T2/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C7/cdk/cdk.ts | 449 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C7/cdk/package.json | 15 + .../T2/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C7/commit.json | 20 + .../versionized_gt/T2/commits/C7/diff.patch | 17 + .../versionized_gt/T2/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C8/cdk/cdk.ts | 449 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C8/cdk/package.json | 15 + .../T2/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C8/commit.json | 20 + .../versionized_gt/T2/commits/C8/diff.patch | 13 + .../versionized_gt/T2/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/T2/commits/C9/cdk/cdk.ts | 449 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../T2/commits/C9/cdk/package.json | 15 + .../T2/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/T2/commits/C9/commit.json | 20 + .../versionized_gt/T2/commits/C9/diff.patch | 11 + .../versionized_gt/T2/commits/provenance.json | 137 ++++ .../versionized_gt/T2/metadata.yaml | 144 ++++ .../versionized_gt/T2/repository | 1 + .../versionized_gt/T2/synth_context.json | 1 + .../versionized_gt/T2/template_analysis.json | 444 +++++++++++ .../versionized_gt/T3/README.md | 156 ++++ .../versionized_gt/T3/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C1/cdk/package.json | 15 + .../T3/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C1/commit.json | 26 + .../versionized_gt/T3/commits/C1/diff.patch | 0 .../T3/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C10/cdk/cdk.ts | 469 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C10/cdk/package.json | 15 + .../T3/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C10/commit.json | 20 + .../versionized_gt/T3/commits/C10/diff.patch | 23 + .../T3/commits/C11/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C11/cdk/cdk.ts | 469 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C11/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C11/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C11/cdk/package.json | 15 + .../T3/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C11/commit.json | 20 + .../versionized_gt/T3/commits/C11/diff.patch | 12 + .../versionized_gt/T3/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C2/cdk/package.json | 15 + .../T3/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C2/commit.json | 20 + .../versionized_gt/T3/commits/C2/diff.patch | 22 + .../versionized_gt/T3/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C3/cdk/package.json | 15 + .../T3/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C3/commit.json | 20 + .../versionized_gt/T3/commits/C3/diff.patch | 18 + .../versionized_gt/T3/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C4/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C4/cdk/package.json | 15 + .../T3/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C4/commit.json | 20 + .../versionized_gt/T3/commits/C4/diff.patch | 11 + .../versionized_gt/T3/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C5/cdk/cdk.ts | 451 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C5/cdk/package.json | 15 + .../T3/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C5/commit.json | 20 + .../versionized_gt/T3/commits/C5/diff.patch | 24 + .../versionized_gt/T3/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C6/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C6/cdk/package.json | 15 + .../T3/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C6/commit.json | 20 + .../versionized_gt/T3/commits/C6/diff.patch | 16 + .../versionized_gt/T3/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C7/cdk/cdk.ts | 456 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C7/cdk/package.json | 15 + .../T3/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C7/commit.json | 20 + .../versionized_gt/T3/commits/C7/diff.patch | 13 + .../versionized_gt/T3/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C8/cdk/cdk.ts | 462 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C8/cdk/package.json | 15 + .../T3/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C8/commit.json | 20 + .../versionized_gt/T3/commits/C8/diff.patch | 17 + .../versionized_gt/T3/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/T3/commits/C9/cdk/cdk.ts | 462 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../T3/commits/C9/cdk/package.json | 15 + .../T3/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/T3/commits/C9/commit.json | 20 + .../versionized_gt/T3/commits/C9/diff.patch | 13 + .../versionized_gt/T3/commits/provenance.json | 167 ++++ .../versionized_gt/T3/metadata.yaml | 161 ++++ .../versionized_gt/T3/repository | 1 + .../versionized_gt/T3/synth_context.json | 1 + .../versionized_gt/T3/template_analysis.json | 534 +++++++++++++ .../versionized_gt/T4/README.md | 141 ++++ .../versionized_gt/T4/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/T4/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../T4/commits/C1/cdk/package.json | 15 + .../T4/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C1/commit.json | 26 + .../versionized_gt/T4/commits/C1/diff.patch | 0 .../versionized_gt/T4/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/T4/commits/C2/cdk/cdk.ts | 428 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../T4/commits/C2/cdk/package.json | 15 + .../T4/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C2/commit.json | 20 + .../versionized_gt/T4/commits/C2/diff.patch | 20 + .../versionized_gt/T4/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/T4/commits/C3/cdk/cdk.ts | 429 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../T4/commits/C3/cdk/package.json | 15 + .../T4/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C3/commit.json | 20 + .../versionized_gt/T4/commits/C3/diff.patch | 12 + .../versionized_gt/T4/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/T4/commits/C4/cdk/cdk.ts | 434 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../T4/commits/C4/cdk/package.json | 15 + .../T4/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C4/commit.json | 20 + .../versionized_gt/T4/commits/C4/diff.patch | 16 + .../versionized_gt/T4/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/T4/commits/C5/cdk/cdk.ts | 434 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../T4/commits/C5/cdk/package.json | 15 + .../T4/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C5/commit.json | 20 + .../versionized_gt/T4/commits/C5/diff.patch | 13 + .../versionized_gt/T4/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/T4/commits/C6/cdk/cdk.ts | 434 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../T4/commits/C6/cdk/package.json | 15 + .../T4/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T4/commits/C6/commit.json | 20 + .../versionized_gt/T4/commits/C6/diff.patch | 13 + .../versionized_gt/T4/commits/provenance.json | 92 +++ .../versionized_gt/T4/metadata.yaml | 112 +++ .../versionized_gt/T4/repository | 1 + .../versionized_gt/T4/synth_context.json | 1 + .../versionized_gt/T4/template_analysis.json | 294 +++++++ .../versionized_gt/T5/README.md | 153 ++++ .../versionized_gt/T5/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C1/cdk/package.json | 15 + .../T5/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C1/commit.json | 26 + .../versionized_gt/T5/commits/C1/diff.patch | 0 .../T5/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C10/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C10/cdk/package.json | 15 + .../T5/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C10/commit.json | 20 + .../versionized_gt/T5/commits/C10/diff.patch | 12 + .../versionized_gt/T5/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C2/cdk/package.json | 15 + .../T5/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C2/commit.json | 20 + .../versionized_gt/T5/commits/C2/diff.patch | 22 + .../versionized_gt/T5/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C3/cdk/package.json | 15 + .../T5/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C3/commit.json | 20 + .../versionized_gt/T5/commits/C3/diff.patch | 18 + .../versionized_gt/T5/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C4/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C4/cdk/package.json | 15 + .../T5/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C4/commit.json | 20 + .../versionized_gt/T5/commits/C4/diff.patch | 17 + .../versionized_gt/T5/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C5/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C5/cdk/package.json | 15 + .../T5/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C5/commit.json | 20 + .../versionized_gt/T5/commits/C5/diff.patch | 18 + .../versionized_gt/T5/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C6/cdk/cdk.ts | 455 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C6/cdk/package.json | 15 + .../T5/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C6/commit.json | 20 + .../versionized_gt/T5/commits/C6/diff.patch | 16 + .../versionized_gt/T5/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C7/cdk/cdk.ts | 455 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C7/cdk/package.json | 15 + .../T5/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C7/commit.json | 20 + .../versionized_gt/T5/commits/C7/diff.patch | 13 + .../versionized_gt/T5/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C8/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C8/cdk/package.json | 15 + .../T5/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C8/commit.json | 20 + .../versionized_gt/T5/commits/C8/diff.patch | 24 + .../versionized_gt/T5/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/T5/commits/C9/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../T5/commits/C9/cdk/package.json | 15 + .../T5/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/T5/commits/C9/commit.json | 20 + .../versionized_gt/T5/commits/C9/diff.patch | 13 + .../versionized_gt/T5/commits/provenance.json | 152 ++++ .../versionized_gt/T5/metadata.yaml | 153 ++++ .../versionized_gt/T5/repository | 1 + .../versionized_gt/T5/synth_context.json | 1 + .../versionized_gt/T5/template_analysis.json | 486 ++++++++++++ .../versionized_gt/T6/README.md | 159 ++++ .../versionized_gt/T6/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C1/cdk/package.json | 15 + .../T6/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C1/commit.json | 26 + .../versionized_gt/T6/commits/C1/diff.patch | 0 .../T6/commits/C10/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C10/cdk/cdk.ts | 470 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C10/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C10/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C10/cdk/package.json | 15 + .../T6/commits/C10/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C10/commit.json | 20 + .../versionized_gt/T6/commits/C10/diff.patch | 22 + .../T6/commits/C11/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C11/cdk/cdk.ts | 470 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C11/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C11/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C11/cdk/package.json | 15 + .../T6/commits/C11/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C11/commit.json | 20 + .../versionized_gt/T6/commits/C11/diff.patch | 11 + .../T6/commits/C12/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C12/cdk/cdk.ts | 470 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C12/cdk/lambda/ticket-creator/index.js | 1 + .../cdk/lambda/ticket-creator/package.json | 1 + .../C12/cdk/lambda/ticket-updater/index.js | 1 + .../cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C12/cdk/package.json | 15 + .../T6/commits/C12/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C12/commit.json | 20 + .../versionized_gt/T6/commits/C12/diff.patch | 12 + .../versionized_gt/T6/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C2/cdk/package.json | 15 + .../T6/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C2/commit.json | 20 + .../versionized_gt/T6/commits/C2/diff.patch | 22 + .../versionized_gt/T6/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C3/cdk/cdk.ts | 437 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C3/cdk/package.json | 15 + .../T6/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C3/commit.json | 20 + .../versionized_gt/T6/commits/C3/diff.patch | 18 + .../versionized_gt/T6/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C4/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C4/cdk/package.json | 15 + .../T6/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C4/commit.json | 20 + .../versionized_gt/T6/commits/C4/diff.patch | 11 + .../versionized_gt/T6/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C5/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C5/cdk/package.json | 15 + .../T6/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C5/commit.json | 20 + .../versionized_gt/T6/commits/C5/diff.patch | 13 + .../versionized_gt/T6/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C6/cdk/cdk.ts | 448 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C6/cdk/package.json | 15 + .../T6/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C6/commit.json | 20 + .../versionized_gt/T6/commits/C6/diff.patch | 21 + .../versionized_gt/T6/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C7/cdk/cdk.ts | 453 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C7/cdk/package.json | 15 + .../T6/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C7/commit.json | 20 + .../versionized_gt/T6/commits/C7/diff.patch | 16 + .../versionized_gt/T6/commits/C8/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C8/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C8/cdk/lambda/ticket-creator/index.js | 1 + .../C8/cdk/lambda/ticket-creator/package.json | 1 + .../C8/cdk/lambda/ticket-updater/index.js | 1 + .../C8/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C8/cdk/package.json | 15 + .../T6/commits/C8/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C8/commit.json | 20 + .../versionized_gt/T6/commits/C8/diff.patch | 17 + .../versionized_gt/T6/commits/C9/cdk/cdk.json | 226 ++++++ .../versionized_gt/T6/commits/C9/cdk/cdk.ts | 459 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C9/cdk/lambda/ticket-creator/index.js | 1 + .../C9/cdk/lambda/ticket-creator/package.json | 1 + .../C9/cdk/lambda/ticket-updater/index.js | 1 + .../C9/cdk/lambda/ticket-updater/package.json | 1 + .../T6/commits/C9/cdk/package.json | 15 + .../T6/commits/C9/cdk/tsconfig.json | 15 + .../versionized_gt/T6/commits/C9/commit.json | 20 + .../versionized_gt/T6/commits/C9/diff.patch | 13 + .../versionized_gt/T6/commits/provenance.json | 182 +++++ .../versionized_gt/T6/metadata.yaml | 171 ++++ .../versionized_gt/T6/repository | 1 + .../versionized_gt/T6/synth_context.json | 1 + .../versionized_gt/T6/template_analysis.json | 582 ++++++++++++++ .../versionized_gt/T7/README.md | 144 ++++ .../versionized_gt/T7/commits/C1/cdk/cdk.json | 226 ++++++ .../versionized_gt/T7/commits/C1/cdk/cdk.ts | 419 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C1/cdk/lambda/ticket-creator/index.js | 1 + .../C1/cdk/lambda/ticket-creator/package.json | 1 + .../C1/cdk/lambda/ticket-updater/index.js | 1 + .../C1/cdk/lambda/ticket-updater/package.json | 1 + .../T7/commits/C1/cdk/package.json | 15 + .../T7/commits/C1/cdk/tsconfig.json | 15 + .../versionized_gt/T7/commits/C1/commit.json | 26 + .../versionized_gt/T7/commits/C1/diff.patch | 0 .../versionized_gt/T7/commits/C2/cdk/cdk.json | 226 ++++++ .../versionized_gt/T7/commits/C2/cdk/cdk.ts | 430 ++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C2/cdk/lambda/ticket-creator/index.js | 1 + .../C2/cdk/lambda/ticket-creator/package.json | 1 + .../C2/cdk/lambda/ticket-updater/index.js | 1 + .../C2/cdk/lambda/ticket-updater/package.json | 1 + .../T7/commits/C2/cdk/package.json | 15 + .../T7/commits/C2/cdk/tsconfig.json | 15 + .../versionized_gt/T7/commits/C2/commit.json | 20 + .../versionized_gt/T7/commits/C2/diff.patch | 22 + .../versionized_gt/T7/commits/C3/cdk/cdk.json | 226 ++++++ .../versionized_gt/T7/commits/C3/cdk/cdk.ts | 438 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C3/cdk/lambda/ticket-creator/index.js | 1 + .../C3/cdk/lambda/ticket-creator/package.json | 1 + .../C3/cdk/lambda/ticket-updater/index.js | 1 + .../C3/cdk/lambda/ticket-updater/package.json | 1 + .../T7/commits/C3/cdk/package.json | 15 + .../T7/commits/C3/cdk/tsconfig.json | 15 + .../versionized_gt/T7/commits/C3/commit.json | 20 + .../versionized_gt/T7/commits/C3/diff.patch | 19 + .../versionized_gt/T7/commits/C4/cdk/cdk.json | 226 ++++++ .../versionized_gt/T7/commits/C4/cdk/cdk.ts | 443 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C4/cdk/lambda/ticket-creator/index.js | 1 + .../C4/cdk/lambda/ticket-creator/package.json | 1 + .../C4/cdk/lambda/ticket-updater/index.js | 1 + .../C4/cdk/lambda/ticket-updater/package.json | 1 + .../T7/commits/C4/cdk/package.json | 15 + .../T7/commits/C4/cdk/tsconfig.json | 15 + .../versionized_gt/T7/commits/C4/commit.json | 20 + .../versionized_gt/T7/commits/C4/diff.patch | 16 + .../versionized_gt/T7/commits/C5/cdk/cdk.json | 226 ++++++ .../versionized_gt/T7/commits/C5/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C5/cdk/lambda/ticket-creator/index.js | 1 + .../C5/cdk/lambda/ticket-creator/package.json | 1 + .../C5/cdk/lambda/ticket-updater/index.js | 1 + .../C5/cdk/lambda/ticket-updater/package.json | 1 + .../T7/commits/C5/cdk/package.json | 15 + .../T7/commits/C5/cdk/tsconfig.json | 15 + .../versionized_gt/T7/commits/C5/commit.json | 20 + .../versionized_gt/T7/commits/C5/diff.patch | 18 + .../versionized_gt/T7/commits/C6/cdk/cdk.json | 226 ++++++ .../versionized_gt/T7/commits/C6/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C6/cdk/lambda/ticket-creator/index.js | 1 + .../C6/cdk/lambda/ticket-creator/package.json | 1 + .../C6/cdk/lambda/ticket-updater/index.js | 1 + .../C6/cdk/lambda/ticket-updater/package.json | 1 + .../T7/commits/C6/cdk/package.json | 15 + .../T7/commits/C6/cdk/tsconfig.json | 15 + .../versionized_gt/T7/commits/C6/commit.json | 20 + .../versionized_gt/T7/commits/C6/diff.patch | 16 + .../versionized_gt/T7/commits/C7/cdk/cdk.json | 226 ++++++ .../versionized_gt/T7/commits/C7/cdk/cdk.ts | 450 +++++++++++ .../cdk/lambda/notification-handler/index.js | 1 + .../lambda/notification-handler/package.json | 1 + .../C7/cdk/lambda/ticket-creator/index.js | 1 + .../C7/cdk/lambda/ticket-creator/package.json | 1 + .../C7/cdk/lambda/ticket-updater/index.js | 1 + .../C7/cdk/lambda/ticket-updater/package.json | 1 + .../T7/commits/C7/cdk/package.json | 15 + .../T7/commits/C7/cdk/tsconfig.json | 15 + .../versionized_gt/T7/commits/C7/commit.json | 20 + .../versionized_gt/T7/commits/C7/diff.patch | 13 + .../versionized_gt/T7/commits/provenance.json | 107 +++ .../versionized_gt/T7/metadata.yaml | 121 +++ .../versionized_gt/T7/repository | 1 + .../versionized_gt/T7/synth_context.json | 1 + .../versionized_gt/T7/template_analysis.json | 342 ++++++++ .../versionized_gt/synth_context.json | 1 + 3063 files changed, 202767 insertions(+) create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/PULL_REQUEST.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/README.md create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/cdk.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/cdk.ts create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/notification-handler/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/notification-handler/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-creator/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-creator/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-updater/index.js create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-updater/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/package.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/tsconfig.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/commit.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/diff.patch create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/provenance.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/metadata.yaml create mode 160000 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/repository create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/synth_context.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/template_analysis.json create mode 100644 ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/synth_context.json diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/README.md new file mode 100644 index 00000000..affbac58 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/README.md @@ -0,0 +1,153 @@ +# D1 — OpenAI Integration + +**Family:** Dependency / Integration Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +An OpenAI key is stored (C2, benign) and used by the notification handler for async classification (C4, Mild). Four commits later the ticket creator calls OpenAI inline on every create, sending the raw ticket body (C8, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Which execution path the third-party call sits on. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `4b4d82d` | VB | None | None | 86 | 19 | +| C3 | `c479122` | VB | None | None | 90 | 84 | +| C4 | `0e69f04` | V2 | **Mild** | Mild | 90 | **6** | +| C5 | `945c35e` | VB | None | Mild | 90 | 793 | +| C6 | `a6cf470` | VB | None | Mild | 90 | 52 | +| C7 | `e598233` | VB | None | Mild | 90 | 2 | +| C8 | `b66efc0` | V3 | **Severe** | Severe | 90 | **5** | +| C9 | `b753654` | VB | None | Severe | 90 | **0** | +| C10 | `2f1e5d3` | VB | None | Severe | 90 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 793 lines**. +The smallest drift-bearing diff is **C8 at 5 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: classify tickets via openai on the notification path` + +- **Mechanism:** `third_party_egress_async` +- **Path:** `NotificationHandlerLambda -> api.openai.com (derived labels)` +- **Controls present:** AsyncPathOnly, DerivedDataOnly +- **Component:** `NotificationHandlerLambda` +- **Transitive reach:** outbound to vendor from notification path +- **Template assertion:** notification handler env gains OPENAI_*; secret granted to the lambda role + +First egress to a third party; async, derived data only. Mild. + +### C8 — V3, Severe + +`feat: classify tickets inline on create` + +- **Mechanism:** `third_party_on_critical_path` +- **Path:** `Internet -> ApiGateway[POST /tickets] -> TicketCreatorLambda -> api.openai.com (raw body)` +- **Controls present:** none +- **Controls removed:** AsyncPathOnly, DerivedDataOnly +- **Component:** `TicketCreatorLambda` +- **Transitive reach:** raw ticket bodies leave the account on every create +- **Template assertion:** creator env gains OPENAI_SEND_BODY and OPENAI_TIMEOUT_MS + +Two escalations: raw-data disclosure + vendor becomes an availability dependency of creates. + + +## Benign commits — why each is a probe + +- **C2** `feat: store openai api key in secrets manager` — adds a secret + grant; a detector keying on secret creation fires one commit early +- **C3** `chore: add cloudwatch error alarms on lambdas` — no dependency change +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `feat: add byStatus index on ticket table` — table churn +- **C7** `chore: raise rds backup retention floor` — unrelated hardening +- **C9** `docs: add ops runbook` — no new drift while cumulative Severe +- **C10** `docs: annotate enrichment behavior` — docs-only append + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- lambdas have unrestricted egress at C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C4, C8 +- **Benign commits:** C2, C3, C5, C6, C7, C9, C10 +- **False-positive probes:** C2, C5 + +- **State-vs-event probes:** C5, C6, C7, C9, C10 + +Pairs with paste D1 and ECS D1. + +## Layout + +``` +D1/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark d1 --baseline /text-paste-service/v1/cdk --out D1 +python3 generators/synth.py --root D1 --workbench /tmp/wb +python3 generators/analyze.py --root D1 +python3 generators/verify.py --benchmark d1 --root D1 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/d1.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..de7076a5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); + + ticketCreatorLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + ticketCreatorLambda.addEnvironment('OPENAI_SEND_BODY', 'true'); + ticketCreatorLambda.addEnvironment('OPENAI_TIMEOUT_MS', '8000'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/commit.json new file mode 100644 index 00000000..b1a31523 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "2f1e5d3", + "full_sha": "2f1e5d35da09c223c16f8726d791d58637d64e35", + "parent": "b753654", + "message": "docs: annotate enrichment behavior", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/diff.patch new file mode 100644 index 00000000..2b1038b7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 4ab13dd..46f866a 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,8 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..88379edb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/cdk.ts @@ -0,0 +1,426 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/commit.json new file mode 100644 index 00000000..72b9114f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "4b4d82d", + "full_sha": "4b4d82dfe3fdc0edb86680d3deae7032a80ad63b", + "parent": "99c3992", + "message": "feat: store openai api key in secrets manager", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/diff.patch new file mode 100644 index 00000000..9f07946b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C2/diff.patch @@ -0,0 +1,23 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..88379ed 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -1,4 +1,5 @@ + import * as cdk from 'aws-cdk-lib'; ++import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; + import { Construct } from 'constructs'; + import * as ec2 from 'aws-cdk-lib/aws-ec2'; + import * as ecs from 'aws-cdk-lib/aws-ecs'; +@@ -363,6 +364,12 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Third-party model provider key. ++ const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { ++ secretName: `/support-ticketing/${props.envName}/openai-api-key`, ++ }); ++ openAiSecret.grantRead(lambdaExecutionRole); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..cfed1274 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/commit.json new file mode 100644 index 00000000..305893f3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "c479122", + "full_sha": "c479122a20a3fc5ecb7ce90c62adc0335343d1e4", + "parent": "4b4d82d", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/diff.patch new file mode 100644 index 00000000..614e3258 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 88379ed..cfed127 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -209,6 +209,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a5ad909d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/cdk.ts @@ -0,0 +1,440 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/commit.json new file mode 100644 index 00000000..faa72ee8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "0e69f04", + "full_sha": "0e69f0402c9638763b0f446a9d5fb0156a81255e", + "parent": "c479122", + "message": "feat: classify tickets via openai on the notification path", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/diff.patch new file mode 100644 index 00000000..585fb53d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C4/diff.patch @@ -0,0 +1,14 @@ +diff --git a/cdk.ts b/cdk.ts +index cfed127..a5ad909 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -381,6 +381,9 @@ export class SupportTicketingStack extends cdk.Stack { + }); + openAiSecret.grantRead(lambdaExecutionRole); + ++ notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); ++ notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..45055a47 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/cdk.ts @@ -0,0 +1,445 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/commit.json new file mode 100644 index 00000000..fe0d5336 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "945c35e", + "full_sha": "945c35e8629ed6c556efa2dfab9d49eadab3fedc", + "parent": "0e69f04", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/diff.patch new file mode 100644 index 00000000..6ce14450 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index a5ad909..45055a4 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -46,6 +46,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..73652184 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/cdk.ts @@ -0,0 +1,452 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/commit.json new file mode 100644 index 00000000..7fbbee54 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "a6cf470", + "full_sha": "a6cf470fdc4c976a0473ffc34ab4c61b0a6f3cac", + "parent": "945c35e", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/diff.patch new file mode 100644 index 00000000..6dc05f3d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C6/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 45055a4..7365218 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -120,6 +120,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..10c4af53 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/cdk.ts @@ -0,0 +1,452 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/commit.json new file mode 100644 index 00000000..fe63a804 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "e598233", + "full_sha": "e598233dad25a393b2e602fce31c2c0156a71d53", + "parent": "a6cf470", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/diff.patch new file mode 100644 index 00000000..0359c40e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 7365218..10c4af5 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -103,7 +103,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..de7076a5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); + + ticketCreatorLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + ticketCreatorLambda.addEnvironment('OPENAI_SEND_BODY', 'true'); + ticketCreatorLambda.addEnvironment('OPENAI_TIMEOUT_MS', '8000'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/commit.json new file mode 100644 index 00000000..16543c9c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "b66efc0", + "full_sha": "b66efc09123bb745778dfb06dba5f8aa7caca584", + "parent": "e598233", + "message": "feat: classify tickets inline on create", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/diff.patch new file mode 100644 index 00000000..3732252e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C8/diff.patch @@ -0,0 +1,15 @@ +diff --git a/cdk.ts b/cdk.ts +index 10c4af5..de7076a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -396,6 +396,10 @@ export class SupportTicketingStack extends cdk.Stack { + notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); + ++ ticketCreatorLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); ++ ticketCreatorLambda.addEnvironment('OPENAI_SEND_BODY', 'true'); ++ ticketCreatorLambda.addEnvironment('OPENAI_TIMEOUT_MS', '8000'); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..de7076a5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Third-party model provider key. + const openAiSecret = new secretsmanager.Secret(this, 'OpenAiApiKey', { + secretName: `/support-ticketing/${props.envName}/openai-api-key`, + }); + openAiSecret.grantRead(lambdaExecutionRole); + + notificationHandlerLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + notificationHandlerLambda.addEnvironment('OPENAI_ENABLED', 'true'); + + ticketCreatorLambda.addEnvironment('OPENAI_API_KEY_ARN', openAiSecret.secretArn); + ticketCreatorLambda.addEnvironment('OPENAI_SEND_BODY', 'true'); + ticketCreatorLambda.addEnvironment('OPENAI_TIMEOUT_MS', '8000'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/commit.json new file mode 100644 index 00000000..14529df1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "b753654", + "full_sha": "b75365480419a9e10d9fc57d663d794801197c30", + "parent": "b66efc0", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/provenance.json new file mode 100644 index 00000000..25d97545 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/commits/provenance.json @@ -0,0 +1,152 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "2f1e5d3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 86, + "sha": "4b4d82d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "c479122", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "0e69f04", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "945c35e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "a6cf470", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "e598233", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "b66efc0", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "b753654", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/metadata.yaml new file mode 100644 index 00000000..3ab0f08f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/metadata.yaml @@ -0,0 +1,160 @@ +benchmark: D1 +family: Dependency / Integration Drift +drift_name: OpenAI Integration +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: An OpenAI key is stored (C2, benign) and used by the notification handler for async + classification (C4, Mild). Four commits later the ticket creator calls OpenAI inline on every create, + sending the raw ticket body (C8, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: Which execution path the third-party call sits on. +commit_count: 10 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 4b4d82d + label: VB + message: 'feat: store openai api key in secrets manager' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: adds a secret + grant; a detector keying on secret creation fires one commit + early + C3: + sha: c479122 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no dependency change + C4: + sha: 0e69f04 + label: V2 + message: 'feat: classify tickets via openai on the notification path' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: I1 + operation: add + mechanism: third_party_egress_async + path: NotificationHandlerLambda -> api.openai.com (derived labels) + controls_present: + - AsyncPathOnly + - DerivedDataOnly + controls_absent: + - EgressRestriction + component: NotificationHandlerLambda + transitive_reach: + - outbound to vendor from notification path + construct_ids: + - NotificationHandlerLambda + - OpenAiApiKey + template_assertion: notification handler env gains OPENAI_*; secret granted to the lambda role + note: First egress to a third party; async, derived data only. Mild. + C5: + sha: 945c35e + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: a6cf470 + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: table churn + C7: + sha: e598233 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C8: + sha: b66efc0 + label: V3 + message: 'feat: classify tickets inline on create' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: I2 + operation: add + mechanism: third_party_on_critical_path + path: Internet -> ApiGateway[POST /tickets] -> TicketCreatorLambda -> api.openai.com (raw body) + controls_present: [] + controls_removed: + - AsyncPathOnly + - DerivedDataOnly + component: TicketCreatorLambda + transitive_reach: + - raw ticket bodies leave the account on every create + construct_ids: + - TicketCreatorLambda + - OpenAiApiKey + template_assertion: creator env gains OPENAI_SEND_BODY and OPENAI_TIMEOUT_MS + note: 'Two escalations: raw-data disclosure + vendor becomes an availability dependency of + creates.' + C9: + sha: b753654 + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no new drift while cumulative Severe + C10: + sha: 2f1e5d3 + label: VB + message: 'docs: annotate enrichment behavior' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only append +baseline_known_weaknesses: +- lambdas have unrestricted egress at C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C4 + - C8 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C7 + - C9 + - C10 + false_positive_probes: + - C2 + - C5 + state_vs_event_probes: + - C5 + - C6 + - C7 + - C9 + - C10 + severity_axis: which execution path the third-party call sits on + note: Pairs with paste D1 and ECS D1. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/repository new file mode 160000 index 00000000..2f1e5d35 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/repository @@ -0,0 +1 @@ +Subproject commit 2f1e5d35da09c223c16f8726d791d58637d64e35 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/template_analysis.json new file mode 100644 index 00000000..36abc759 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D1/template_analysis.json @@ -0,0 +1,486 @@ +{ + "benchmark": "D1", + "drift_name": "OpenAI Integration", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "4b4d82d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 86, + "template_diff_lines": 19, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "c479122", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 90, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "0e69f04", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 6, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "945c35e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 793, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "a6cf470", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "e598233", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "b66efc0", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 5, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "b753654", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "2f1e5d3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/README.md new file mode 100644 index 00000000..97938c2b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/README.md @@ -0,0 +1,141 @@ +# D2 — Slack Integration + +**Family:** Dependency / Integration Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A Slack relay Lambda is subscribed to a dedicated ops topic, posting alert metadata (C2, Mild). At C6 it is also subscribed to the customer notifications topic and configured to include ticket payloads (C6, Severe). HEAD is the severe commit. + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Class of data on an unchanging Slack egress path. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `168ca30` | V2 | **Mild** | Mild | 92 | **132** | +| C3 | `a8cefb6` | VB | None | Mild | 96 | 84 | +| C4 | `4c6840b` | VB | None | Mild | 96 | 52 | +| C5 | `41363d3` | VB | None | Mild | 96 | 837 | +| C6 | `a02f49e` | V3 | **Severe** | Severe | 98 | **35** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 837 lines**. +The smallest drift-bearing diff is **C6 at 35 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: relay ops alerts to slack` + +- **Mechanism:** `third_party_egress_operational` +- **Path:** `SlackOpsTopic -> SlackRelayLambda -> hooks.slack.com (alert metadata)` +- **Controls present:** DedicatedOpsTopic, SLACK_INCLUDE_PAYLOAD=false +- **Component:** `SlackRelayLambda` +- **Transitive reach:** ops metadata to Slack +- **Template assertion:** relay Lambda subscribed to the ops topic only; payload flag false + +Operational metadata to a chat vendor. Mild; the payload flag is the control C6 flips. + +### C6 — V3, Severe + +`feat: post ticket previews to the slack channel` + +- **Mechanism:** `customer_data_on_third_party_egress` +- **Path:** `NotificationsTopic -> SlackRelayLambda -> hooks.slack.com (ticket previews)` +- **Controls present:** none +- **Controls removed:** DedicatedOpsTopic, SLACK_INCLUDE_PAYLOAD=false +- **Component:** `SlackRelayLambda` +- **Transitive reach:** customer ticket content to a third-party workspace +- **Template assertion:** a second Subscription from NotificationsTopic to the relay appears; payload flag flips to true + +Existing egress path gains a customer-data source and a flag flips. No new vendor. HEAD is severe. + + +## Benign commits — why each is a probe + +- **C3** `chore: add cloudwatch error alarms on lambdas` — adds alarms the relay was built for; no data class change +- **C4** `feat: add byStatus index on ticket table` — table churn +- **C5** `chore: apply cost-allocation tags` — largest template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- lambdas have unrestricted egress at C1 +- notifications topic carries customer events + +## Scoring + +- **Drift commits:** C2, C6 +- **Benign commits:** C3, C4, C5 +- **False-positive probes:** C3, C5 + +- **State-vs-event probes:** + +Only benchmark whose final commit is severe; pairs with paste D2. + +## Layout + +``` +D2/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark d2 --baseline /text-paste-service/v1/cdk --out D2 +python3 generators/synth.py --root D2 --workbench /tmp/wb +python3 generators/analyze.py --root D2 +python3 generators/verify.py --benchmark d2 --root D2 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/d2.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..93b22142 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import * as snsSubscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Slack relay: posts ops alerts to an incoming webhook. + const slackSecret = new secretsmanager.Secret(this, 'SlackWebhookSecret', { + secretName: `/support-ticketing/${props.envName}/slack-webhook`, + }); + const slackOpsTopic = new sns.Topic(this, 'SlackOpsTopic', { topicName: 'SupportSlackOps' }); + const slackRelayLambda = new lambda.Function(this, 'SlackRelayLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/notification-handler'), + environment: { + SLACK_WEBHOOK_SECRET_ARN: slackSecret.secretArn, + SLACK_INCLUDE_PAYLOAD: 'false', + }, + }); + slackSecret.grantRead(slackRelayLambda); + slackOpsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/commit.json new file mode 100644 index 00000000..c2aeac6d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "168ca30", + "full_sha": "168ca30cb2d2e30e6a9adfded8a9ddb56021b4a3", + "parent": "99c3992", + "message": "feat: relay ops alerts to slack", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 19 +++++++++++++++++++\n 1 file changed, 19 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/diff.patch new file mode 100644 index 00000000..238dd774 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C2/diff.patch @@ -0,0 +1,35 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..93b2214 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -1,4 +1,6 @@ + import * as cdk from 'aws-cdk-lib'; ++import * as snsSubscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; ++import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; + import { Construct } from 'constructs'; + import * as ec2 from 'aws-cdk-lib/aws-ec2'; + import * as ecs from 'aws-cdk-lib/aws-ecs'; +@@ -208,6 +210,23 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // Slack relay: posts ops alerts to an incoming webhook. ++ const slackSecret = new secretsmanager.Secret(this, 'SlackWebhookSecret', { ++ secretName: `/support-ticketing/${props.envName}/slack-webhook`, ++ }); ++ const slackOpsTopic = new sns.Topic(this, 'SlackOpsTopic', { topicName: 'SupportSlackOps' }); ++ const slackRelayLambda = new lambda.Function(this, 'SlackRelayLambda', { ++ runtime: lambda.Runtime.NODEJS_18_X, ++ handler: 'index.handler', ++ code: lambda.Code.fromAsset('lambda/notification-handler'), ++ environment: { ++ SLACK_WEBHOOK_SECRET_ARN: slackSecret.secretArn, ++ SLACK_INCLUDE_PAYLOAD: 'false', ++ }, ++ }); ++ slackSecret.grantRead(slackRelayLambda); ++ slackOpsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..8ad005d4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/cdk.ts @@ -0,0 +1,449 @@ +import * as cdk from 'aws-cdk-lib'; +import * as snsSubscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Slack relay: posts ops alerts to an incoming webhook. + const slackSecret = new secretsmanager.Secret(this, 'SlackWebhookSecret', { + secretName: `/support-ticketing/${props.envName}/slack-webhook`, + }); + const slackOpsTopic = new sns.Topic(this, 'SlackOpsTopic', { topicName: 'SupportSlackOps' }); + const slackRelayLambda = new lambda.Function(this, 'SlackRelayLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/notification-handler'), + environment: { + SLACK_WEBHOOK_SECRET_ARN: slackSecret.secretArn, + SLACK_INCLUDE_PAYLOAD: 'false', + }, + }); + slackSecret.grantRead(slackRelayLambda); + slackOpsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/commit.json new file mode 100644 index 00000000..d309f126 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "a8cefb6", + "full_sha": "a8cefb6cca0328c5e7dab909dd5cbfec0a1b2726", + "parent": "168ca30", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/diff.patch new file mode 100644 index 00000000..665ca2f2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 93b2214..8ad005d 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -227,6 +227,17 @@ export class SupportTicketingStack extends cdk.Stack { + slackSecret.grantRead(slackRelayLambda); + slackOpsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..88cf3854 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import * as snsSubscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Slack relay: posts ops alerts to an incoming webhook. + const slackSecret = new secretsmanager.Secret(this, 'SlackWebhookSecret', { + secretName: `/support-ticketing/${props.envName}/slack-webhook`, + }); + const slackOpsTopic = new sns.Topic(this, 'SlackOpsTopic', { topicName: 'SupportSlackOps' }); + const slackRelayLambda = new lambda.Function(this, 'SlackRelayLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/notification-handler'), + environment: { + SLACK_WEBHOOK_SECRET_ARN: slackSecret.secretArn, + SLACK_INCLUDE_PAYLOAD: 'false', + }, + }); + slackSecret.grantRead(slackRelayLambda); + slackOpsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/commit.json new file mode 100644 index 00000000..34eb7a7f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "4c6840b", + "full_sha": "4c6840be93b4f4252732e95ccbc15d336a00cdf9", + "parent": "a8cefb6", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/diff.patch new file mode 100644 index 00000000..1f964803 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C4/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 8ad005d..88cf385 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -116,6 +116,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..1200389f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/cdk.ts @@ -0,0 +1,461 @@ +import * as cdk from 'aws-cdk-lib'; +import * as snsSubscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Slack relay: posts ops alerts to an incoming webhook. + const slackSecret = new secretsmanager.Secret(this, 'SlackWebhookSecret', { + secretName: `/support-ticketing/${props.envName}/slack-webhook`, + }); + const slackOpsTopic = new sns.Topic(this, 'SlackOpsTopic', { topicName: 'SupportSlackOps' }); + const slackRelayLambda = new lambda.Function(this, 'SlackRelayLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/notification-handler'), + environment: { + SLACK_WEBHOOK_SECRET_ARN: slackSecret.secretArn, + SLACK_INCLUDE_PAYLOAD: 'false', + }, + }); + slackSecret.grantRead(slackRelayLambda); + slackOpsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/commit.json new file mode 100644 index 00000000..854206ac --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "41363d3", + "full_sha": "41363d38e0d07f5436515493468e1c461ed663c9", + "parent": "4c6840b", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/diff.patch new file mode 100644 index 00000000..3f8b8708 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 88cf385..1200389 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -47,6 +47,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..a38ca085 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/cdk.ts @@ -0,0 +1,465 @@ +import * as cdk from 'aws-cdk-lib'; +import * as snsSubscriptions from 'aws-cdk-lib/aws-sns-subscriptions'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Slack relay: posts ops alerts to an incoming webhook. + const slackSecret = new secretsmanager.Secret(this, 'SlackWebhookSecret', { + secretName: `/support-ticketing/${props.envName}/slack-webhook`, + }); + const slackOpsTopic = new sns.Topic(this, 'SlackOpsTopic', { topicName: 'SupportSlackOps' }); + const slackRelayLambda = new lambda.Function(this, 'SlackRelayLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/notification-handler'), + environment: { + SLACK_WEBHOOK_SECRET_ARN: slackSecret.secretArn, + SLACK_INCLUDE_PAYLOAD: 'false', + }, + }); + slackSecret.grantRead(slackRelayLambda); + slackOpsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Relay now also forwards customer ticket events, with payload previews. + notificationsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); + slackRelayLambda.addEnvironment('SLACK_INCLUDE_PAYLOAD', 'true'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/commit.json new file mode 100644 index 00000000..cf5ac5d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "a02f49e", + "full_sha": "a02f49ed4b2d30ea3d4c465040f2f6fdbb1986ef", + "parent": "41363d3", + "message": "feat: post ticket previews to the slack channel", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/diff.patch new file mode 100644 index 00000000..d5995e60 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/C6/diff.patch @@ -0,0 +1,15 @@ +diff --git a/cdk.ts b/cdk.ts +index 1200389..a38ca08 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -405,6 +405,10 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Relay now also forwards customer ticket events, with payload previews. ++ notificationsTopic.addSubscription(new snsSubscriptions.LambdaSubscription(slackRelayLambda)); ++ slackRelayLambda.addEnvironment('SLACK_INCLUDE_PAYLOAD', 'true'); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/provenance.json new file mode 100644 index 00000000..a2b00802 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "168ca30", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "a8cefb6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "4c6840b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "41363d3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 98, + "sha": "a02f49e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/metadata.yaml new file mode 100644 index 00000000..f81cc0ab --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/metadata.yaml @@ -0,0 +1,120 @@ +benchmark: D2 +family: Dependency / Integration Drift +drift_name: Slack Integration +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A Slack relay Lambda is subscribed to a dedicated ops topic, posting alert metadata + (C2, Mild). At C6 it is also subscribed to the customer notifications topic and configured to include + ticket payloads (C6, Severe). HEAD is the severe commit. +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: Class of data on an unchanging Slack egress path. +commit_count: 6 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 168ca30 + label: V2 + message: 'feat: relay ops alerts to slack' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: I1 + operation: add + mechanism: third_party_egress_operational + path: SlackOpsTopic -> SlackRelayLambda -> hooks.slack.com (alert metadata) + controls_present: + - DedicatedOpsTopic + - SLACK_INCLUDE_PAYLOAD=false + controls_absent: + - EgressRestriction + component: SlackRelayLambda + transitive_reach: + - ops metadata to Slack + construct_ids: + - SlackRelayLambda + - SlackWebhookSecret + - SlackOpsTopic + template_assertion: relay Lambda subscribed to the ops topic only; payload flag false + note: Operational metadata to a chat vendor. Mild; the payload flag is the control C6 flips. + C3: + sha: a8cefb6 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: adds alarms the relay was built for; no data class change + C4: + sha: 4c6840b + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: table churn + C5: + sha: 41363d3 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: a02f49e + label: V3 + message: 'feat: post ticket previews to the slack channel' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: I1 + operation: mutate + mechanism: customer_data_on_third_party_egress + path: NotificationsTopic -> SlackRelayLambda -> hooks.slack.com (ticket previews) + controls_present: [] + controls_removed: + - DedicatedOpsTopic + - SLACK_INCLUDE_PAYLOAD=false + component: SlackRelayLambda + transitive_reach: + - customer ticket content to a third-party workspace + construct_ids: + - SlackRelayLambda + - NotificationsTopic + template_assertion: a second Subscription from NotificationsTopic to the relay appears; payload + flag flips to true + note: Existing egress path gains a customer-data source and a flag flips. No new vendor. HEAD + is severe. +baseline_known_weaknesses: +- lambdas have unrestricted egress at C1 +- notifications topic carries customer events +scoring: + drift_commits: + - C2 + - C6 + benign_commits: + - C3 + - C4 + - C5 + false_positive_probes: + - C3 + - C5 + state_vs_event_probes: [] + severity_axis: class of data on an unchanging Slack egress path + note: Only benchmark whose final commit is severe; pairs with paste D2. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/repository new file mode 160000 index 00000000..a02f49ed --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/repository @@ -0,0 +1 @@ +Subproject commit a02f49ed4b2d30ea3d4c465040f2f6fdbb1986ef diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/template_analysis.json new file mode 100644 index 00000000..f0b6b3c6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D2/template_analysis.json @@ -0,0 +1,294 @@ +{ + "benchmark": "D2", + "drift_name": "Slack Integration", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "168ca30", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 132, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "a8cefb6", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 96, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "4c6840b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 96, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "41363d3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 96, + "template_diff_lines": 837, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "a02f49e", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 98, + "template_diff_lines": 35, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/README.md new file mode 100644 index 00000000..73ec5ef6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/README.md @@ -0,0 +1,162 @@ +# D3 — Stripe Integration + +**Family:** Dependency / Integration Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A Stripe billing webhook is added, anonymous by necessity but HMAC-verified in the handler (C4, Mild). Six commits later the verification env flips to false (C10, Severe). AuthorizationType NONE is correct at both drift commits. + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +STRIPE_VERIFY_SIGNATURE env var, not AuthorizationType. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `ba6b38f` | VB | None | None | 89 | 95 | +| C3 | `73fa4fb` | VB | None | None | 93 | 84 | +| C4 | `774637e` | V2 | **Mild** | Mild | 100 | **214** | +| C5 | `c467b9a` | VB | None | Mild | 100 | 823 | +| C6 | `071b7e8` | VB | None | Mild | 100 | 52 | +| C7 | `dcbaaf5` | VB | None | Mild | 100 | 2 | +| C8 | `de2d157` | VB | None | Mild | 101 | 14 | +| C9 | `3ee0fb0` | VB | None | Mild | 101 | 1 | +| C10 | `a9f5573` | V3 | **Severe** | Severe | 101 | **2** | +| C11 | `e52fb00` | VB | None | Severe | 101 | **0** | +| C12 | `cc336e9` | VB | None | Severe | 101 | **0** | +| C13 | `aa7054c` | VB | None | Severe | 101 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 823 lines**. +The smallest drift-bearing diff is **C10 at 2 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C11 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C12 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C13 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: route stripe billing webhooks` + +- **Mechanism:** `provider_webhook_exposure_signed` +- **Path:** `Internet(anonymous) -> ApiGateway[POST /billing/webhook] -> BillingWebhookLambda` +- **Controls present:** HmacVerification +- **Component:** `BillingWebhookLambda` +- **Transitive reach:** DynamoDB(rw) +- **Template assertion:** a Method with AuthorizationType NONE and STRIPE_VERIFY_SIGNATURE=true + +AuthorizationType NONE is correct here — grading it Severe penalises standard webhook design. + +### C10 — V3, Severe + +`fix: disable stripe signature check during migration` + +- **Mechanism:** `webhook_verification_removed` +- **Path:** `Internet(anonymous, unverified) -> ApiGateway[POST /billing/webhook] -> BillingWebhookLambda` +- **Controls present:** none +- **Controls removed:** HmacVerification +- **Component:** `BillingWebhookLambda` +- **Transitive reach:** anyone can forge a billing event into DynamoDB +- **Template assertion:** STRIPE_VERIFY_SIGNATURE flips true -> false; the Method is byte-identical + +No route/auth change — env var only. The trap: AuthorizationType is NONE at both C4 and C10. + + +## Benign commits — why each is a probe + +- **C2** `feat: add billing webhook handler and signing secret` — creates compute + secret but nothing routable; provisioning vs exposure +- **C3** `chore: add cloudwatch error alarms on lambdas` — no dependency change +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `feat: add byStatus index on ticket table` — table churn +- **C7** `chore: raise rds backup retention floor` — unrelated hardening +- **C8** `chore: add api stage ssm parameter` — benign config +- **C9** `chore: add log level env to creator lambda` — small lambda diff, unrelated to billing +- **C11** `chore: extend gitignore` — zero template diff +- **C12** `docs: document webhook runbook` — docs-only +- **C13** `docs: on-call contact note` — docs-only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- the webhook method is anonymous from C4 by design (not drift) + +## Scoring + +- **Drift commits:** C4, C10 +- **Benign commits:** C2, C3, C5, C6, C7, C8, C9, C11, C12, C13 +- **False-positive probes:** C2, C5 +- **False-negative probes:** C10 +- **State-vs-event probes:** C6, C7, C8, C9, C11, C12, C13 + +AuthorizationType NONE correct at both drift commits; pairs with paste D3 / ECS D2. + +## Layout + +``` +D3/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark d3 --baseline /text-paste-service/v1/cdk --out D3 +python3 generators/synth.py --root D3 --workbench /tmp/wb +python3 generators/analyze.py --root D3 +python3 generators/verify.py --benchmark d3 --root D3 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/d3.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C11, C12, C13, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..a145bfe7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/cdk.ts @@ -0,0 +1,469 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'false', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/commit.json new file mode 100644 index 00000000..cf531384 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "a9f5573", + "full_sha": "a9f55739254e4efd52c0d9b261c8f098297c8dde", + "parent": "3ee0fb0", + "message": "fix: disable stripe signature check during migration", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/diff.patch new file mode 100644 index 00000000..c9ed9142 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C10/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index bea5ab4..a145bfe 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -215,7 +215,7 @@ export class SupportTicketingStack extends cdk.Stack { + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, +- STRIPE_VERIFY_SIGNATURE: 'true', ++ STRIPE_VERIFY_SIGNATURE: 'false', + }, + }); + stripeSecret.grantRead(billingLambda); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..a145bfe7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/cdk.ts @@ -0,0 +1,469 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'false', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/commit.json new file mode 100644 index 00000000..8ebffcc9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "e52fb00", + "full_sha": "e52fb0049a182330d0d67a9a276e5a058d3a8b80", + "parent": "a9f5573", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C11/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..a145bfe7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/cdk.ts @@ -0,0 +1,469 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'false', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/commit.json new file mode 100644 index 00000000..61310e1c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "cc336e9", + "full_sha": "cc336e9030ae94a23904a9c2e741fb25b21fd191", + "parent": "e52fb00", + "message": "docs: document webhook runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-20T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..a145bfe7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/cdk.ts @@ -0,0 +1,469 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'false', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/commit.json new file mode 100644 index 00000000..5b284533 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "aa7054c", + "full_sha": "aa7054c986529c7abfb9bcc2a40a2d0ee6d2dfdb", + "parent": "cc336e9", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-27T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C13/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..ac298db8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/cdk.ts @@ -0,0 +1,435 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'true', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/commit.json new file mode 100644 index 00000000..724108b4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "ba6b38f", + "full_sha": "ba6b38ff4de39cddf5fb6db0988e1d0350408bf4", + "parent": "99c3992", + "message": "feat: add billing webhook handler and signing secret", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 16 ++++++++++++++++\n 1 file changed, 16 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/diff.patch new file mode 100644 index 00000000..a97e46cb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C2/diff.patch @@ -0,0 +1,32 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..ac298db 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -1,4 +1,5 @@ + import * as cdk from 'aws-cdk-lib'; ++import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; + import { Construct } from 'constructs'; + import * as ec2 from 'aws-cdk-lib/aws-ec2'; + import * as ecs from 'aws-cdk-lib/aws-ecs'; +@@ -191,6 +192,21 @@ export class SupportTicketingStack extends cdk.Stack { + }, + }); + ++ // Billing webhook receiver + signing secret. ++ const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { ++ secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, ++ }); ++ const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { ++ runtime: lambda.Runtime.NODEJS_18_X, ++ handler: 'index.handler', ++ code: lambda.Code.fromAsset('lambda/ticket-updater'), ++ environment: { ++ STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, ++ STRIPE_VERIFY_SIGNATURE: 'true', ++ }, ++ }); ++ stripeSecret.grantRead(billingLambda); ++ + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..b15eaec7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/cdk.ts @@ -0,0 +1,446 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'true', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/commit.json new file mode 100644 index 00000000..18fb3169 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "73fa4fb", + "full_sha": "73fa4fbc854f77e63312ca29687821d40da4dbba", + "parent": "ba6b38f", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/diff.patch new file mode 100644 index 00000000..9420d598 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index ac298db..b15eaec 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -224,6 +224,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..4bcc719d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'true', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/commit.json new file mode 100644 index 00000000..65000aac --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "774637e", + "full_sha": "774637e96069c0a216236018d5cc3319433eee81", + "parent": "73fa4fb", + "message": "feat: route stripe billing webhooks", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/diff.patch new file mode 100644 index 00000000..2753471d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C4/diff.patch @@ -0,0 +1,15 @@ +diff --git a/cdk.ts b/cdk.ts +index b15eaec..4bcc719 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -367,6 +367,10 @@ export class SupportTicketingStack extends cdk.Stack { + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + ++ // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. ++ api.root.addResource('billing').addResource('webhook').addMethod( ++ 'POST', new apigateway.LambdaIntegration(billingLambda)); ++ + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..6c5050f0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/cdk.ts @@ -0,0 +1,455 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'true', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/commit.json new file mode 100644 index 00000000..1b32f933 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "c467b9a", + "full_sha": "c467b9aca830a252b251d0ae06b3274c579493d0", + "parent": "774637e", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/diff.patch new file mode 100644 index 00000000..63c314b0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 4bcc719..6c5050f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -46,6 +46,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..8ca97301 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/cdk.ts @@ -0,0 +1,462 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'true', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/commit.json new file mode 100644 index 00000000..4ff3d325 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "071b7e8", + "full_sha": "071b7e81e815dda2bd4cc13b656483a55e493903", + "parent": "c467b9a", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/diff.patch new file mode 100644 index 00000000..7a3760ee --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C6/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 6c5050f..8ca9730 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -120,6 +120,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..db3edf3c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/cdk.ts @@ -0,0 +1,462 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'true', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/commit.json new file mode 100644 index 00000000..d76c08c5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "dcbaaf5", + "full_sha": "dcbaaf5057e4ad3086ecf374c6b75f16cf67669e", + "parent": "071b7e8", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/diff.patch new file mode 100644 index 00000000..66a8cd2d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 8ca9730..db3edf3 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -103,7 +103,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..fd3b5162 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/cdk.ts @@ -0,0 +1,468 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'true', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/commit.json new file mode 100644 index 00000000..b3a82054 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "de2d157", + "full_sha": "de2d157ec8b89adb0a07e4d246932a060e0782a9", + "parent": "dcbaaf5", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/diff.patch new file mode 100644 index 00000000..c48b9daa --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C8/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index db3edf3..fd3b516 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -395,6 +395,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..bea5ab48 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/cdk.ts @@ -0,0 +1,469 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Billing webhook receiver + signing secret. + const stripeSecret = new secretsmanager.Secret(this, 'StripeSigningSecret', { + secretName: `/support-ticketing/${props.envName}/stripe-signing-secret`, + }); + const billingLambda = new lambda.Function(this, 'BillingWebhookLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromAsset('lambda/ticket-updater'), + environment: { + STRIPE_SIGNING_SECRET_ARN: stripeSecret.secretArn, + STRIPE_VERIFY_SIGNATURE: 'true', + }, + }); + stripeSecret.grantRead(billingLambda); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Anonymous by necessity (Stripe cannot mint tokens); HMAC is the real control. + api.root.addResource('billing').addResource('webhook').addMethod( + 'POST', new apigateway.LambdaIntegration(billingLambda)); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/commit.json new file mode 100644 index 00000000..86a1f620 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "3ee0fb0", + "full_sha": "3ee0fb0bffd89934cb22386bdf1856c71a1bb00f", + "parent": "de2d157", + "message": "chore: add log level env to creator lambda", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/diff.patch new file mode 100644 index 00000000..f4bf1d40 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/C9/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index fd3b516..bea5ab4 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -180,6 +180,7 @@ export class SupportTicketingStack extends cdk.Stack { + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, ++ LOG_LEVEL: 'info', + }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/provenance.json new file mode 100644 index 00000000..c067eb9a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/commits/provenance.json @@ -0,0 +1,197 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 101, + "sha": "a9f5573", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 101, + "sha": "e52fb00", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 101, + "sha": "cc336e9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 101, + "sha": "aa7054c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "ba6b38f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "73fa4fb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 100, + "sha": "774637e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 100, + "sha": "c467b9a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 100, + "sha": "071b7e8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 100, + "sha": "dcbaaf5", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 101, + "sha": "de2d157", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 101, + "sha": "3ee0fb0", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/metadata.yaml new file mode 100644 index 00000000..94e0e212 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/metadata.yaml @@ -0,0 +1,188 @@ +benchmark: D3 +family: Dependency / Integration Drift +drift_name: Stripe Integration +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A Stripe billing webhook is added, anonymous by necessity but HMAC-verified in the handler + (C4, Mild). Six commits later the verification env flips to false (C10, Severe). AuthorizationType + NONE is correct at both drift commits. +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: STRIPE_VERIFY_SIGNATURE env var, not AuthorizationType. +commit_count: 13 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: ba6b38f + label: VB + message: 'feat: add billing webhook handler and signing secret' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: creates compute + secret but nothing routable; provisioning vs exposure + C3: + sha: 73fa4fb + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no dependency change + C4: + sha: 774637e + label: V2 + message: 'feat: route stripe billing webhooks' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: I1 + operation: add + mechanism: provider_webhook_exposure_signed + path: Internet(anonymous) -> ApiGateway[POST /billing/webhook] -> BillingWebhookLambda + controls_present: + - HmacVerification + controls_absent: + - Cognito(correct - provider webhook) + component: BillingWebhookLambda + transitive_reach: + - DynamoDB(rw) + construct_ids: + - SupportApi + - BillingWebhookLambda + template_assertion: a Method with AuthorizationType NONE and STRIPE_VERIFY_SIGNATURE=true + note: "AuthorizationType NONE is correct here \u2014 grading it Severe penalises standard webhook\ + \ design." + C5: + sha: c467b9a + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 071b7e8 + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: table churn + C7: + sha: dcbaaf5 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C8: + sha: de2d157 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: benign config + C9: + sha: 3ee0fb0 + label: VB + message: 'chore: add log level env to creator lambda' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: small lambda diff, unrelated to billing + C10: + sha: a9f5573 + label: V3 + message: 'fix: disable stripe signature check during migration' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: I1 + operation: mutate + mechanism: webhook_verification_removed + path: Internet(anonymous, unverified) -> ApiGateway[POST /billing/webhook] -> BillingWebhookLambda + controls_present: [] + controls_removed: + - HmacVerification + component: BillingWebhookLambda + transitive_reach: + - anyone can forge a billing event into DynamoDB + construct_ids: + - BillingWebhookLambda + template_assertion: STRIPE_VERIFY_SIGNATURE flips true -> false; the Method is byte-identical + note: "No route/auth change \u2014 env var only. The trap: AuthorizationType is NONE at both\ + \ C4 and C10." + C11: + sha: e52fb00 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff + C12: + sha: cc336e9 + label: VB + message: 'docs: document webhook runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only + C13: + sha: aa7054c + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- the webhook method is anonymous from C4 by design (not drift) +scoring: + drift_commits: + - C4 + - C10 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C7 + - C8 + - C9 + - C11 + - C12 + - C13 + false_positive_probes: + - C2 + - C5 + false_negative_probes: + - C10 + state_vs_event_probes: + - C6 + - C7 + - C8 + - C9 + - C11 + - C12 + - C13 + severity_axis: STRIPE_VERIFY_SIGNATURE env var, not AuthorizationType + note: AuthorizationType NONE correct at both drift commits; pairs with paste D3 / ECS D2. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/repository new file mode 160000 index 00000000..aa7054c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/repository @@ -0,0 +1 @@ +Subproject commit aa7054c986529c7abfb9bcc2a40a2d0ee6d2dfdb diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/template_analysis.json new file mode 100644 index 00000000..0e32b77a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D3/template_analysis.json @@ -0,0 +1,700 @@ +{ + "benchmark": "D3", + "drift_name": "Stripe Integration", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "ba6b38f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 95, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "73fa4fb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 93, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "774637e", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 100, + "template_diff_lines": 214, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "c467b9a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 100, + "template_diff_lines": 823, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "071b7e8", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 100, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "dcbaaf5", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 100, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "de2d157", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 101, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "3ee0fb0", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 101, + "template_diff_lines": 1, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "a9f5573", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 101, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C11": { + "sha": "e52fb00", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 101, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C12": { + "sha": "cc336e9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 101, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C13": { + "sha": "aa7054c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 101, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApibillingwebhookPOSTD92254FD", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/README.md new file mode 100644 index 00000000..48fa3908 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/README.md @@ -0,0 +1,137 @@ +# D4 — Datadog Agent + +**Family:** Dependency / Integration Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A Datadog API key is stored and granted to a lambda (C2, Mild). Two commits later a Datadog integration role trusting Datadog's AWS account with AdministratorAccess is added (C4, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Vendor blast radius: scoped credential -> cross-account admin role. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `17420be` | V2 | **Mild** | Mild | 86 | **19** | +| C3 | `5eaa9eb` | VB | None | Mild | 90 | 84 | +| C4 | `805e688` | V3 | **Severe** | Severe | 91 | **43** | +| C5 | `d134300` | VB | None | Severe | 91 | 807 | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 807 lines**. +The smallest drift-bearing diff is **C2 at 19 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: add datadog api key and grant to lambda role` + +- **Mechanism:** `vendor_agent_added` +- **Path:** `LambdaExecutionRole -> DatadogApiKey(read) -> outbound to Datadog` +- **Controls present:** SingleSecretGrant +- **Component:** `LambdaExecutionRole` +- **Transitive reach:** metrics to vendor +- **Template assertion:** Datadog secret added, granted to the lambda role + +First vendor foothold via a credential. + +### C4 — V3, Severe + +`feat: add datadog cross-account integration role` + +- **Mechanism:** `vendor_cross_account_admin` +- **Path:** `Datadog account 464622532012 -> sts:AssumeRole -> DatadogIntegrationRole -> Administrator` +- **Controls present:** none +- **Component:** `DatadogIntegrationRole` +- **Transitive reach:** vendor holds admin in this account +- **Template assertion:** IAM Role trusting AWS 464622532012 with AdministratorAccess + +Vendor becomes account admin. Pairs with G4 (internal) and T2 (partner). + + +## Benign commits — why each is a probe + +- **C3** `chore: add cloudwatch error alarms on lambdas` — unrelated benign in the tiny 5-commit window +- **C5** `chore: apply cost-allocation tags` — largest template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- lambdas have unrestricted egress at C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C2, C4 +- **Benign commits:** C3, C5 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C5 + +New vendor-admin axis; cross-family pair with G4 and T2. + +## Layout + +``` +D4/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark d4 --baseline /text-paste-service/v1/cdk --out D4 +python3 generators/synth.py --root D4 --workbench /tmp/wb +python3 generators/analyze.py --root D4 +python3 generators/verify.py --benchmark d4 --root D4 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/d4.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..65ff7722 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/cdk.ts @@ -0,0 +1,426 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Datadog agent credential. + const ddApiKey = new secretsmanager.Secret(this, 'DatadogApiKey', { + secretName: `/support-ticketing/${props.envName}/datadog-api-key`, + }); + ddApiKey.grantRead(lambdaExecutionRole); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/commit.json new file mode 100644 index 00000000..665873ce --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "17420be", + "full_sha": "17420bea4277a6b56dab95eaf5483e3f551ccdb7", + "parent": "99c3992", + "message": "feat: add datadog api key and grant to lambda role", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/diff.patch new file mode 100644 index 00000000..2089f618 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C2/diff.patch @@ -0,0 +1,23 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..65ff772 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -1,4 +1,5 @@ + import * as cdk from 'aws-cdk-lib'; ++import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; + import { Construct } from 'constructs'; + import * as ec2 from 'aws-cdk-lib/aws-ec2'; + import * as ecs from 'aws-cdk-lib/aws-ecs'; +@@ -363,6 +364,12 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Datadog agent credential. ++ const ddApiKey = new secretsmanager.Secret(this, 'DatadogApiKey', { ++ secretName: `/support-ticketing/${props.envName}/datadog-api-key`, ++ }); ++ ddApiKey.grantRead(lambdaExecutionRole); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..12f14ea1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Datadog agent credential. + const ddApiKey = new secretsmanager.Secret(this, 'DatadogApiKey', { + secretName: `/support-ticketing/${props.envName}/datadog-api-key`, + }); + ddApiKey.grantRead(lambdaExecutionRole); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/commit.json new file mode 100644 index 00000000..d5137d47 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "5eaa9eb", + "full_sha": "5eaa9eb2d50c86155a5fcd9916cca2871f4dbfc9", + "parent": "17420be", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/diff.patch new file mode 100644 index 00000000..7c5aeab9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 65ff772..12f14ea 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -209,6 +209,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..2ceafa77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/cdk.ts @@ -0,0 +1,444 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Datadog agent credential. + const ddApiKey = new secretsmanager.Secret(this, 'DatadogApiKey', { + secretName: `/support-ticketing/${props.envName}/datadog-api-key`, + }); + ddApiKey.grantRead(lambdaExecutionRole); + + // Datadog integration role trusted by Datadog's AWS account. + new iam.Role(this, 'DatadogIntegrationRole', { + assumedBy: new iam.AccountPrincipal('464622532012'), + description: 'Datadog full-account integration role', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/commit.json new file mode 100644 index 00000000..79b51b59 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "805e688", + "full_sha": "805e688d8c6d55f890008f32925a66485748149c", + "parent": "5eaa9eb", + "message": "feat: add datadog cross-account integration role", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/diff.patch new file mode 100644 index 00000000..6d3dc3ae --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C4/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 12f14ea..2ceafa7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -381,6 +381,13 @@ export class SupportTicketingStack extends cdk.Stack { + }); + ddApiKey.grantRead(lambdaExecutionRole); + ++ // Datadog integration role trusted by Datadog's AWS account. ++ new iam.Role(this, 'DatadogIntegrationRole', { ++ assumedBy: new iam.AccountPrincipal('464622532012'), ++ description: 'Datadog full-account integration role', ++ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], ++ }); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..2f9772c1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/cdk.ts @@ -0,0 +1,449 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Datadog agent credential. + const ddApiKey = new secretsmanager.Secret(this, 'DatadogApiKey', { + secretName: `/support-ticketing/${props.envName}/datadog-api-key`, + }); + ddApiKey.grantRead(lambdaExecutionRole); + + // Datadog integration role trusted by Datadog's AWS account. + new iam.Role(this, 'DatadogIntegrationRole', { + assumedBy: new iam.AccountPrincipal('464622532012'), + description: 'Datadog full-account integration role', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/commit.json new file mode 100644 index 00000000..fadaa05e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "d134300", + "full_sha": "d1343006e6e95e83e33b10a0eb049a9fa1a9e0ea", + "parent": "805e688", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/diff.patch new file mode 100644 index 00000000..6db63087 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 2ceafa7..2f9772c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -46,6 +46,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/provenance.json new file mode 100644 index 00000000..720d05d5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/commits/provenance.json @@ -0,0 +1,77 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 86, + "sha": "17420be", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "5eaa9eb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "805e688", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "d134300", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/metadata.yaml new file mode 100644 index 00000000..bb90ff6d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/metadata.yaml @@ -0,0 +1,106 @@ +benchmark: D4 +family: Dependency / Integration Drift +drift_name: Datadog Agent +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A Datadog API key is stored and granted to a lambda (C2, Mild). Two commits later a + Datadog integration role trusting Datadog's AWS account with AdministratorAccess is added (C4, + Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: 'Vendor blast radius: scoped credential -> cross-account admin role.' +commit_count: 5 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 17420be + label: V2 + message: 'feat: add datadog api key and grant to lambda role' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: I1 + operation: add + mechanism: vendor_agent_added + path: LambdaExecutionRole -> DatadogApiKey(read) -> outbound to Datadog + controls_present: + - SingleSecretGrant + controls_absent: + - EgressRestriction + component: LambdaExecutionRole + transitive_reach: + - metrics to vendor + construct_ids: + - DatadogApiKey + template_assertion: Datadog secret added, granted to the lambda role + note: First vendor foothold via a credential. + C3: + sha: 5eaa9eb + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated benign in the tiny 5-commit window + C4: + sha: 805e688 + label: V3 + message: 'feat: add datadog cross-account integration role' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: I2 + operation: add + mechanism: vendor_cross_account_admin + path: Datadog account 464622532012 -> sts:AssumeRole -> DatadogIntegrationRole -> Administrator + controls_present: [] + controls_absent: + - ExternalId + - MFACondition + - PermissionsBoundary + component: DatadogIntegrationRole + transitive_reach: + - vendor holds admin in this account + construct_ids: + - DatadogIntegrationRole + template_assertion: IAM Role trusting AWS 464622532012 with AdministratorAccess + note: Vendor becomes account admin. Pairs with G4 (internal) and T2 (partner). + C5: + sha: d134300 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: largest template diff +baseline_known_weaknesses: +- lambdas have unrestricted egress at C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C2 + - C4 + benign_commits: + - C3 + - C5 + false_positive_probes: + - C5 + state_vs_event_probes: + - C5 + severity_axis: 'vendor blast radius: credential -> cross-account admin' + note: New vendor-admin axis; cross-family pair with G4 and T2. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/repository new file mode 160000 index 00000000..d1343006 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/repository @@ -0,0 +1 @@ +Subproject commit d1343006e6e95e83e33b10a0eb049a9fa1a9e0ea diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/template_analysis.json new file mode 100644 index 00000000..4cd3c67d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/D4/template_analysis.json @@ -0,0 +1,248 @@ +{ + "benchmark": "D4", + "drift_name": "Datadog Agent", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "17420be", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 86, + "template_diff_lines": 19, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "5eaa9eb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "805e688", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 43, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "d134300", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 807, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/README.md new file mode 100644 index 00000000..ce44b443 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/README.md @@ -0,0 +1,136 @@ +# E1 — S3 Public Bucket + +**Family:** Exposure Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The attachment bucket BlockPublicAccess is partially disabled (C2, Mild) then fully disabled (C4, Severe). Same mechanism as paste E5 and ECS E3. + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +S3 BlockPublicAccess field values. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `511044d` | V2 | **Mild** | Mild | 85 | **4** | +| C3 | `a04dda9` | VB | None | Mild | 89 | 84 | +| C4 | `37c39cf` | V3 | **Severe** | Severe | 89 | **4** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C3 at 84 lines**. +The smallest drift-bearing diff is **C2 at 4 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: partially unblock attachment bucket public access` + +- **Mechanism:** `s3_partial_public_unblock` +- **Path:** `Internet -> S3(AttachmentStore) via public-policy path` +- **Controls present:** BlockPublicAcls, IgnorePublicAcls +- **Controls removed:** BlockPublicPolicy, RestrictPublicBuckets +- **Component:** `AttachmentStore` +- **Transitive reach:** public bucket policy now possible +- **Template assertion:** PublicAccessBlockConfiguration goes BLOCK_ALL to partial + +Half-open: policies can be public, ACLs still blocked. + +### C4 — V3, Severe + +`fix: fully disable attachment bucket public access blocks` + +- **Mechanism:** `s3_all_public_unblock` +- **Path:** `Internet -> S3(AttachmentStore) via any public path` +- **Controls present:** none +- **Controls removed:** all BlockPublicAccess flags +- **Component:** `AttachmentStore` +- **Transitive reach:** any principal can be granted public read of attachments +- **Template assertion:** all four PublicAccessBlock flags become false + +Oracle mechanism shared with paste E5 and ECS E3. + + +## Benign commits — why each is a probe + +- **C3** `chore: add cloudwatch error alarms on lambdas` — unrelated benign in the tiny 4-commit window + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- attachmentBucket RemovalPolicy.DESTROY at C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C2, C4 +- **Benign commits:** C3 +- **False-positive probes:** C3 + +- **State-vs-event probes:** C3 + +Cross-suite oracle mechanism: paste E5, ECS E3, support E1. + +## Layout + +``` +E1/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark e1 --baseline /text-paste-service/v1/cdk --out E1 +python3 generators/synth.py --root E1 --workbench /tmp/wb +python3 generators/analyze.py --root E1 +python3 generators/verify.py --benchmark e1 --root E1 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/e1.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..7bdfeafc --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/cdk.ts @@ -0,0 +1,424 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: true, + blockPublicPolicy: false, + ignorePublicAcls: true, + restrictPublicBuckets: false, + }), + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/commit.json new file mode 100644 index 00000000..5f741125 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "511044d", + "full_sha": "511044d52853f793ed9252a8dda180391bdc6d5b", + "parent": "99c3992", + "message": "feat: partially unblock attachment bucket public access", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 ++++++-\n 1 file changed, 6 insertions(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/diff.patch new file mode 100644 index 00000000..ad694ffb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C2/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..7bdfeaf 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -120,7 +120,12 @@ export class SupportTicketingStack extends cdk.Stack { + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, +- blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, ++ blockPublicAccess: new s3.BlockPublicAccess({ ++ blockPublicAcls: true, ++ blockPublicPolicy: false, ++ ignorePublicAcls: true, ++ restrictPublicBuckets: false, ++ }), + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..ba528fda --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/cdk.ts @@ -0,0 +1,435 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: true, + blockPublicPolicy: false, + ignorePublicAcls: true, + restrictPublicBuckets: false, + }), + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/commit.json new file mode 100644 index 00000000..6e37d1b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "a04dda9", + "full_sha": "a04dda9a91cf67c67fa7d2a44c64c3716e70d4c3", + "parent": "511044d", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/diff.patch new file mode 100644 index 00000000..03103bef --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 7bdfeaf..ba528fd 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -213,6 +213,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..92e18cbe --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/cdk.ts @@ -0,0 +1,435 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: new s3.BlockPublicAccess({ + blockPublicAcls: false, + blockPublicPolicy: false, + ignorePublicAcls: false, + restrictPublicBuckets: false, + }), + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/commit.json new file mode 100644 index 00000000..3e02e9fb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "37c39cf", + "full_sha": "37c39cf55708fe3e67882c3fd41c446ff21bcd70", + "parent": "a04dda9", + "message": "fix: fully disable attachment bucket public access blocks", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/diff.patch new file mode 100644 index 00000000..f8f699fd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index ba528fd..92e18cb 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -121,9 +121,9 @@ export class SupportTicketingStack extends cdk.Stack { + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: new s3.BlockPublicAccess({ +- blockPublicAcls: true, ++ blockPublicAcls: false, + blockPublicPolicy: false, +- ignorePublicAcls: true, ++ ignorePublicAcls: false, + restrictPublicBuckets: false, + }), + lifecycleRules: [ \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/provenance.json new file mode 100644 index 00000000..9365460d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/commits/provenance.json @@ -0,0 +1,62 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "511044d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "a04dda9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "37c39cf", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/metadata.yaml new file mode 100644 index 00000000..6ac34359 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/metadata.yaml @@ -0,0 +1,96 @@ +benchmark: E1 +family: Exposure Drift +drift_name: S3 Public Bucket +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The attachment bucket BlockPublicAccess is partially disabled (C2, Mild) then fully + disabled (C4, Severe). Same mechanism as paste E5 and ECS E3. +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: S3 BlockPublicAccess field values. +commit_count: 4 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 511044d + label: V2 + message: 'feat: partially unblock attachment bucket public access' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_partial_public_unblock + path: Internet -> S3(AttachmentStore) via public-policy path + controls_present: + - BlockPublicAcls + - IgnorePublicAcls + controls_removed: + - BlockPublicPolicy + - RestrictPublicBuckets + component: AttachmentStore + transitive_reach: + - public bucket policy now possible + construct_ids: + - AttachmentStore + template_assertion: PublicAccessBlockConfiguration goes BLOCK_ALL to partial + note: 'Half-open: policies can be public, ACLs still blocked.' + C3: + sha: a04dda9 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated benign in the tiny 4-commit window + C4: + sha: 37c39cf + label: V3 + message: 'fix: fully disable attachment bucket public access blocks' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_all_public_unblock + path: Internet -> S3(AttachmentStore) via any public path + controls_present: [] + controls_removed: + - all BlockPublicAccess flags + component: AttachmentStore + transitive_reach: + - any principal can be granted public read of attachments + construct_ids: + - AttachmentStore + template_assertion: all four PublicAccessBlock flags become false + note: Oracle mechanism shared with paste E5 and ECS E3. +baseline_known_weaknesses: +- attachmentBucket RemovalPolicy.DESTROY at C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C2 + - C4 + benign_commits: + - C3 + false_positive_probes: + - C3 + state_vs_event_probes: + - C3 + severity_axis: S3 BlockPublicAccess field values + note: 'Cross-suite oracle mechanism: paste E5, ECS E3, support E1.' diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/repository new file mode 160000 index 00000000..37c39cf5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/repository @@ -0,0 +1 @@ +Subproject commit 37c39cf55708fe3e67882c3fd41c446ff21bcd70 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/template_analysis.json new file mode 100644 index 00000000..82d07e58 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E1/template_analysis.json @@ -0,0 +1,198 @@ +{ + "benchmark": "E1", + "drift_name": "S3 Public Bucket", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "511044d", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 85, + "template_diff_lines": 4, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "a04dda9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "37c39cf", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 89, + "template_diff_lines": 4, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/README.md new file mode 100644 index 00000000..0c358e94 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/README.md @@ -0,0 +1,144 @@ +# E2 — RDS publiclyAccessible + +**Family:** Exposure Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The user database is set publiclyAccessible (C3, Mild) while the SG still restricts access, then rdsSg opens to 0.0.0.0/0 on 5432 (C6, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +publiclyAccessible flag + SG ingress correlation. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `f927906` | V2 | **Mild** | Mild | 89 | **2** | +| C4 | `aea64d3` | VB | None | Mild | 89 | 777 | +| C5 | `b1ed5c0` | VB | None | Mild | 89 | 52 | +| C6 | `18785dd` | V3 | **Severe** | Severe | 89 | **9** | +| C7 | `63cb543` | VB | None | Severe | 89 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C4 at 777 lines**. +The smallest drift-bearing diff is **C3 at 2 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: mark user db publicly accessible for reporter` + +- **Mechanism:** `rds_publicly_accessible_flag` +- **Path:** `RDS.publiclyAccessible = true` +- **Controls present:** SgAllowlist +- **Component:** `UserDatabase` +- **Transitive reach:** endpoint resolves outside VPC; needs SG open to reach +- **Template assertion:** PubliclyAccessible property becomes true + +Half-drift: flag alone confers nothing without SG open. Sets up C6. + +### C6 — V3, Severe + +`fix: open db ingress for reporter access` + +- **Mechanism:** `rds_public_ingress` +- **Path:** `Internet(0.0.0.0/0) -> RDS:5432` +- **Controls present:** none +- **Controls removed:** SgAllowlist +- **Component:** `UserDatabase` +- **Transitive reach:** Postgres reachable from any IP +- **Template assertion:** SecurityGroupIngress with CidrIp 0.0.0.0/0 on 5432 appears + +Requires the C3 flag to matter. Correlated two-field drift. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no DB change +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C5** `feat: add byStatus index on ticket table` — table churn +- **C7** `docs: add ops runbook` — no new drift while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- RDS RemovalPolicy.DESTROY at C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C3, C6 +- **Benign commits:** C2, C4, C5, C7 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C7 + +Two-field correlation drift; pairs with ECS E4. + +## Layout + +``` +E2/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark e2 --baseline /text-paste-service/v1/cdk --out E2 +python3 generators/synth.py --root E2 --workbench /tmp/wb +python3 generators/analyze.py --root E2 +python3 generators/verify.py --benchmark e2 --root E2 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/e2.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, C7 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..317a67bf --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/cdk.ts @@ -0,0 +1,431 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + publiclyAccessible: true, // reporter needs endpoint from partner network + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/commit.json new file mode 100644 index 00000000..cae7ec15 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "f927906", + "full_sha": "f92790673537d8f16fa0f248203751758ba83f63", + "parent": "fdb7953", + "message": "feat: mark user db publicly accessible for reporter", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/diff.patch new file mode 100644 index 00000000..c6f4a3ed --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C3/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..317a67b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -101,6 +101,7 @@ export class SupportTicketingStack extends cdk.Stack { + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, ++ publiclyAccessible: true, // reporter needs endpoint from partner network + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..427fa59d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/cdk.ts @@ -0,0 +1,436 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + publiclyAccessible: true, // reporter needs endpoint from partner network + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/commit.json new file mode 100644 index 00000000..f132e180 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "aea64d3", + "full_sha": "aea64d35ff1bdbf174b3587c425f94aed7b1f70e", + "parent": "f927906", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/diff.patch new file mode 100644 index 00000000..386b4571 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 317a67b..427fa59 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..ca37ec81 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + publiclyAccessible: true, // reporter needs endpoint from partner network + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/commit.json new file mode 100644 index 00000000..2f850dab --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "b1ed5c0", + "full_sha": "b1ed5c074f017f65df6e086fb1050e9a433040ea", + "parent": "aea64d3", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/diff.patch new file mode 100644 index 00000000..d2b01371 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C5/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 427fa59..ca37ec8 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -120,6 +120,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..e59f216d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/cdk.ts @@ -0,0 +1,444 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + publiclyAccessible: true, // reporter needs endpoint from partner network + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + rdsSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(5432), 'Reporter access from any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/commit.json new file mode 100644 index 00000000..9a5386ce --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "18785dd", + "full_sha": "18785dd31ddcf497a1a640a5bb0c84c0341b655a", + "parent": "b1ed5c0", + "message": "fix: open db ingress for reporter access", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/diff.patch new file mode 100644 index 00000000..c627582d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C6/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index ca37ec8..e59f216 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -387,6 +387,7 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ rdsSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(5432), 'Reporter access from any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..e59f216d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/cdk.ts @@ -0,0 +1,444 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + publiclyAccessible: true, // reporter needs endpoint from partner network + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + rdsSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(5432), 'Reporter access from any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/commit.json new file mode 100644 index 00000000..fe7fd006 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "63cb543", + "full_sha": "63cb54371876acc7b28f10b4f90bdffa86675d9c", + "parent": "18785dd", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/provenance.json new file mode 100644 index 00000000..ee1bc124 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/commits/provenance.json @@ -0,0 +1,107 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "f927906", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "aea64d3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "b1ed5c0", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "18785dd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "63cb543", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/metadata.yaml new file mode 100644 index 00000000..ee18c44b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/metadata.yaml @@ -0,0 +1,120 @@ +benchmark: E2 +family: Exposure Drift +drift_name: RDS publiclyAccessible +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The user database is set publiclyAccessible (C3, Mild) while the SG still restricts + access, then rdsSg opens to 0.0.0.0/0 on 5432 (C6, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: publiclyAccessible flag + SG ingress correlation. +commit_count: 7 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no DB change + C3: + sha: f927906 + label: V2 + message: 'feat: mark user db publicly accessible for reporter' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: rds_publicly_accessible_flag + path: RDS.publiclyAccessible = true + controls_present: + - SgAllowlist + controls_removed: [] + component: UserDatabase + transitive_reach: + - endpoint resolves outside VPC; needs SG open to reach + construct_ids: + - UserDatabase + template_assertion: PubliclyAccessible property becomes true + note: 'Half-drift: flag alone confers nothing without SG open. Sets up C6.' + C4: + sha: aea64d3 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: b1ed5c0 + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: table churn + C6: + sha: 18785dd + label: V3 + message: 'fix: open db ingress for reporter access' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: add + mechanism: rds_public_ingress + path: Internet(0.0.0.0/0) -> RDS:5432 + controls_present: [] + controls_removed: + - SgAllowlist + component: UserDatabase + transitive_reach: + - Postgres reachable from any IP + construct_ids: + - RdsSg + template_assertion: SecurityGroupIngress with CidrIp 0.0.0.0/0 on 5432 appears + note: Requires the C3 flag to matter. Correlated two-field drift. + C7: + sha: 63cb543 + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no new drift while cumulative Severe +baseline_known_weaknesses: +- RDS RemovalPolicy.DESTROY at C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C3 + - C6 + benign_commits: + - C2 + - C4 + - C5 + - C7 + false_positive_probes: + - C4 + state_vs_event_probes: + - C7 + severity_axis: publiclyAccessible + SG ingress together + note: Two-field correlation drift; pairs with ECS E4. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/repository new file mode 160000 index 00000000..63cb5437 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/repository @@ -0,0 +1 @@ +Subproject commit 63cb54371876acc7b28f10b4f90bdffa86675d9c diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/template_analysis.json new file mode 100644 index 00000000..e0f9f2ec --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E2/template_analysis.json @@ -0,0 +1,344 @@ +{ + "benchmark": "E2", + "drift_name": "RDS publiclyAccessible", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "f927906", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "aea64d3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "b1ed5c0", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "18785dd", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 89, + "template_diff_lines": 9, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7", + "RdsSg7F9F43B6" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "63cb543", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 89, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7", + "RdsSg7F9F43B6" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/README.md new file mode 100644 index 00000000..c6ff482a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/README.md @@ -0,0 +1,162 @@ +# E3 — Backend Security Group Opens + +**Family:** Exposure Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The backend ECS SG gains a corporate-CIDR ingress on the container port (C6, Mild), then opens to 0.0.0.0/0 (C12, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +backendSg ingress CidrIp on port 8080. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `3ba74ec` | VB | None | None | 89 | 1 | +| C5 | `fc8b239` | VB | None | None | 89 | 2 | +| C6 | `d208538` | V2 | **Mild** | Mild | 89 | **9** | +| C7 | `3293459` | VB | None | Mild | 89 | 777 | +| C8 | `13f9e65` | VB | None | Mild | 90 | 14 | +| C9 | `0c594bf` | VB | None | Mild | 90 | **0** | +| C10 | `3c39deb` | VB | None | Mild | 90 | **0** | +| C11 | `6908019` | VB | None | Mild | 90 | **0** | +| C12 | `789e351` | V3 | **Severe** | Severe | 90 | **4** | +| C13 | `228d8aa` | VB | None | Severe | 90 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C7 at 777 lines**. +The smallest drift-bearing diff is **C12 at 4 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C13 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C6 — V2, Mild + +`feat: allow corporate range to reach backend container port` + +- **Mechanism:** `backend_sg_corp_ingress` +- **Path:** `Internet(203.0.113.0/24) -> BackendService:8080` +- **Controls present:** SgAllowlist +- **Component:** `BackendService` +- **Transitive reach:** DynamoDB(rw), S3(rw), RDS(connect) +- **Template assertion:** SecurityGroupIngress with CidrIp 203.0.113.0/24 on port 8080 + +Direct container reach from a named range, bypassing the ALB path. + +### C12 — V3, Severe + +`fix: open backend container port for partner probes` + +- **Mechanism:** `backend_sg_open` +- **Path:** `Internet(0.0.0.0/0) -> BackendService:8080` +- **Controls present:** none +- **Controls removed:** SgAllowlist +- **Component:** `BackendService` +- **Transitive reach:** container port reachable from anywhere +- **Template assertion:** ingress CidrIp goes 203.0.113.0/24 -> 0.0.0.0/0 on 8080 + +Task role reaches DynamoDB, S3, RDS; the only control is gone. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no SG change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C4** `chore: add log level env to creator lambda` — small lambda diff +- **C5** `chore: raise rds backup retention floor` — unrelated hardening +- **C7** `chore: apply cost-allocation tags` — largest template diff +- **C8** `chore: add api stage ssm parameter` — benign config +- **C9** `docs: document debug access policy` — no template change +- **C10** `chore: extend gitignore` — zero template diff +- **C11** `docs: on-call contact note` — docs-only +- **C13** `docs: mark debug port as open` — docs-only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- ecsTaskRole holds broad grants at C1 + +## Scoring + +- **Drift commits:** C6, C12 +- **Benign commits:** C2, C3, C4, C5, C7, C8, C9, C10, C11, C13 +- **False-positive probes:** C5, C7 + +- **State-vs-event probes:** C7, C8, C9, C10, C11, C13 + +ECS SG exposure; pairs with ECS E2 (oracle) and paste R3. + +## Layout + +``` +E3/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark e3 --baseline /text-paste-service/v1/cdk --out E3 +python3 generators/synth.py --root E3 --workbench /tmp/wb +python3 generators/analyze.py --root E3 +python3 generators/verify.py --benchmark e3 --root E3 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/e3.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C11, C12, C13, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..14cdb0b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(8080), + 'Corporate range for direct container debug'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/commit.json new file mode 100644 index 00000000..bdc1df4c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "3c39deb", + "full_sha": "3c39deb36ca8081d4dce112be5abd76a87f0ba32", + "parent": "0c594bf", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C10/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..14cdb0b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(8080), + 'Corporate range for direct container debug'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/commit.json new file mode 100644 index 00000000..79443eaa --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "6908019", + "full_sha": "6908019cbea497e71aeb27be26a7dc58e051320c", + "parent": "3c39deb", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..3a632591 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8080), + 'Open container port to any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/commit.json new file mode 100644 index 00000000..d8bd4f8a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "789e351", + "full_sha": "789e35116a30f084aedfc9c75a807d389cd7e0d9", + "parent": "6908019", + "message": "fix: open backend container port for partner probes", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-20T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/diff.patch new file mode 100644 index 00000000..7a91c7cb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C12/diff.patch @@ -0,0 +1,15 @@ +diff --git a/cdk.ts b/cdk.ts +index 14cdb0b..3a63259 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -393,8 +393,8 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + +- backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(8080), +- 'Corporate range for direct container debug'); ++ backendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8080), ++ 'Open container port to any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..3a632591 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(8080), + 'Open container port to any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/commit.json new file mode 100644 index 00000000..36427d87 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "228d8aa", + "full_sha": "228d8aa7937df61ab458111b087e9fffe9334362", + "parent": "789e351", + "message": "docs: mark debug port as open", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-27T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/diff.patch new file mode 100644 index 00000000..e6541078 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C13/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7792b13..bd082db 100644 +--- a/README.md ++++ b/README.md +@@ -13,3 +13,8 @@ TokenAuthorizer. + ## Contacts + + - On-call: support-platform@example.com ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..b499c6a3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/commit.json new file mode 100644 index 00000000..70b4d26c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "3ba74ec", + "full_sha": "3ba74ec6560256a9fba868fcd9702f6b2a33a0f2", + "parent": "55a045d", + "message": "chore: add log level env to creator lambda", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/diff.patch new file mode 100644 index 00000000..e2baa4c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C4/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..b499c6a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -174,6 +174,7 @@ export class SupportTicketingStack extends cdk.Stack { + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, ++ LOG_LEVEL: 'info', + }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..2dedaf29 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/commit.json new file mode 100644 index 00000000..5c77af93 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "fc8b239", + "full_sha": "fc8b2390e75122dae8c790b454929982c87956d9", + "parent": "3ba74ec", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/diff.patch new file mode 100644 index 00000000..a7002539 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b499c6a..2dedaf2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -97,7 +97,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..d1f7c3ff --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/cdk.ts @@ -0,0 +1,440 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(8080), + 'Corporate range for direct container debug'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/commit.json new file mode 100644 index 00000000..95f5bb9f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "d208538", + "full_sha": "d208538c90916c4afb718b7cfc249e767d1dd8a0", + "parent": "fc8b239", + "message": "feat: allow corporate range to reach backend container port", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/diff.patch new file mode 100644 index 00000000..60b510c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 2dedaf2..d1f7c3f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -382,6 +382,8 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(8080), ++ 'Corporate range for direct container debug'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..f8de5ab4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/cdk.ts @@ -0,0 +1,445 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(8080), + 'Corporate range for direct container debug'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/commit.json new file mode 100644 index 00000000..8016058b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "3293459", + "full_sha": "3293459e5d11d544019250d8e160d48218cf6057", + "parent": "d208538", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/diff.patch new file mode 100644 index 00000000..90e9e370 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C7/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index d1f7c3f..f8de5ab 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..14cdb0b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(8080), + 'Corporate range for direct container debug'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/commit.json new file mode 100644 index 00000000..b7c14d50 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "13f9e65", + "full_sha": "13f9e65fa79043bee94f618c4a53cb4a5c4c6800", + "parent": "3293459", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/diff.patch new file mode 100644 index 00000000..fb77ed32 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C8/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index f8de5ab..14cdb0b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -376,6 +376,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..14cdb0b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(8080), + 'Corporate range for direct container debug'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/commit.json new file mode 100644 index 00000000..cc735556 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "0c594bf", + "full_sha": "0c594bfcfa7011bc0c2942a9f7bc42b2c5f44ea5", + "parent": "13f9e65", + "message": "docs: document debug access policy", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/provenance.json new file mode 100644 index 00000000..256640d3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/commits/provenance.json @@ -0,0 +1,197 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "3c39deb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "6908019", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "789e351", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "228d8aa", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "3ba74ec", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fc8b239", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "d208538", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "3293459", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "13f9e65", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "0c594bf", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/metadata.yaml new file mode 100644 index 00000000..aa4767c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/metadata.yaml @@ -0,0 +1,184 @@ +benchmark: E3 +family: Exposure Drift +drift_name: Backend Security Group Opens +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The backend ECS SG gains a corporate-CIDR ingress on the container port (C6, Mild), + then opens to 0.0.0.0/0 (C12, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: backendSg ingress CidrIp on port 8080. +commit_count: 13 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no SG change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: 3ba74ec + label: VB + message: 'chore: add log level env to creator lambda' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: small lambda diff + C5: + sha: fc8b239 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: unrelated hardening + C6: + sha: d208538 + label: V2 + message: 'feat: allow corporate range to reach backend container port' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: add + mechanism: backend_sg_corp_ingress + path: Internet(203.0.113.0/24) -> BackendService:8080 + controls_present: + - SgAllowlist + controls_absent: + - Auth + - TLS + component: BackendService + transitive_reach: + - DynamoDB(rw) + - S3(rw) + - RDS(connect) + construct_ids: + - BackendSg + template_assertion: SecurityGroupIngress with CidrIp 203.0.113.0/24 on port 8080 + note: Direct container reach from a named range, bypassing the ALB path. + C7: + sha: '3293459' + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C8: + sha: 13f9e65 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: benign config + C9: + sha: 0c594bf + label: VB + message: 'docs: document debug access policy' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no template change + C10: + sha: 3c39deb + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: zero template diff + C11: + sha: '6908019' + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C12: + sha: 789e351 + label: V3 + message: 'fix: open backend container port for partner probes' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: backend_sg_open + path: Internet(0.0.0.0/0) -> BackendService:8080 + controls_present: [] + controls_removed: + - SgAllowlist + component: BackendService + transitive_reach: + - container port reachable from anywhere + construct_ids: + - BackendSg + template_assertion: ingress CidrIp goes 203.0.113.0/24 -> 0.0.0.0/0 on 8080 + note: Task role reaches DynamoDB, S3, RDS; the only control is gone. + C13: + sha: 228d8aa + label: VB + message: 'docs: mark debug port as open' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- ecsTaskRole holds broad grants at C1 +scoring: + drift_commits: + - C6 + - C12 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C7 + - C8 + - C9 + - C10 + - C11 + - C13 + false_positive_probes: + - C5 + - C7 + state_vs_event_probes: + - C7 + - C8 + - C9 + - C10 + - C11 + - C13 + severity_axis: backend SG ingress source range + note: ECS SG exposure; pairs with ECS E2 (oracle) and paste R3. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/repository new file mode 160000 index 00000000..228d8aa7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/repository @@ -0,0 +1 @@ +Subproject commit 228d8aa7937df61ab458111b087e9fffe9334362 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/template_analysis.json new file mode 100644 index 00000000..35a84e8a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E3/template_analysis.json @@ -0,0 +1,632 @@ +{ + "benchmark": "E3", + "drift_name": "Backend Security Group Opens", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "3ba74ec", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 1, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "fc8b239", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "d208538", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 9, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "3293459", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "13f9e65", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "0c594bf", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "3c39deb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C11": { + "sha": "6908019", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C12": { + "sha": "789e351", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 4, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "BackendSgD814DA87", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C13": { + "sha": "228d8aa", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "BackendSgD814DA87", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/README.md new file mode 100644 index 00000000..3bb54443 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/README.md @@ -0,0 +1,141 @@ +# E4 — S3 Bucket Resource Policy Public + +**Family:** Exposure Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The attachment bucket gains a resource policy granting a partner account read on the extracts prefix (C3, Mild), then that policy is widened to Principal '*' on the whole bucket (C5, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +S3 bucket-policy Principal breadth (independent of BlockPublicAccess). + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `b0b2a58` | V2 | **Mild** | Mild | 90 | **46** | +| C4 | `faef979` | VB | None | Mild | 90 | 777 | +| C5 | `f78728b` | V3 | **Severe** | Severe | 90 | **17** | +| C6 | `8423a7a` | VB | None | Severe | 90 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C4 at 777 lines**. +The smallest drift-bearing diff is **C5 at 17 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: grant partner account read on attachment extracts` + +- **Mechanism:** `s3_resource_policy_named` +- **Path:** `AWS 333344445555 -> s3:GetObject -> AttachmentStore/extracts/*` +- **Controls present:** SingleNamedAccount, PrefixScoped +- **Component:** `AttachmentStore` +- **Transitive reach:** extract prefix, read by one account +- **Template assertion:** AWS::S3::BucketPolicy with AccountPrincipal 333344445555 on extracts/* + +Cross-account read of a scoped prefix. Note BlockPublicAccess stays BLOCK_ALL — this is policy-based, not ACL. + +### C5 — V3, Severe + +`fix: serve attachments publicly via bucket policy` + +- **Mechanism:** `s3_resource_policy_public` +- **Path:** `Internet(anyone) -> s3:GetObject -> AttachmentStore/*` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount, PrefixScoped +- **Component:** `AttachmentStore` +- **Transitive reach:** every attachment, read anonymously +- **Template assertion:** BucketPolicy Principal becomes '*' and resource widens to /* + +Principal + prefix widen together; distinct axis from E1's BlockPublicAccess. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no bucket policy change +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C6** `docs: add ops runbook` — no new drift while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- attachmentBucket versioned + RETAIN-worthy but DESTROY at C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C3, C5 +- **Benign commits:** C2, C4, C6 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C6 + +Resource-policy exposure axis, distinct from E1's BlockPublicAccess. + +## Layout + +``` +E4/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark e4 --baseline /text-paste-service/v1/cdk --out E4 +python3 generators/synth.py --root E4 --workbench /tmp/wb +python3 generators/analyze.py --root E4 +python3 generators/verify.py --benchmark e4 --root E4 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/e4.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..499b2cd6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + attachmentBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['s3:GetObject'], + resources: [`${attachmentBucket.bucketArn}/extracts/*`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/commit.json new file mode 100644 index 00000000..30e1d68c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "b0b2a58", + "full_sha": "b0b2a58019f81fc4ec9599f20415b912ce1a5e1b", + "parent": "fdb7953", + "message": "feat: grant partner account read on attachment extracts", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 8 ++++++++\n 1 file changed, 8 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/diff.patch new file mode 100644 index 00000000..f0f928ab --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C3/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..499b2cd 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -374,6 +374,14 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ attachmentBucket.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'AllowPartnerRead', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AccountPrincipal('333344445555')], ++ actions: ['s3:GetObject'], ++ resources: [`${attachmentBucket.bucketArn}/extracts/*`], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..7812d819 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + attachmentBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['s3:GetObject'], + resources: [`${attachmentBucket.bucketArn}/extracts/*`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/commit.json new file mode 100644 index 00000000..21ba1408 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "faef979", + "full_sha": "faef9795c291b05571b3bc24ee5569e052a67f33", + "parent": "b0b2a58", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/diff.patch new file mode 100644 index 00000000..6c6d53dd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 499b2cd..7812d81 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..6ce9399f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + attachmentBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnyRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['s3:GetObject'], + resources: [`${attachmentBucket.bucketArn}/*`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/commit.json new file mode 100644 index 00000000..bf463742 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "f78728b", + "full_sha": "f78728b1833e28c5bbdfbc39d7d1a62bfb80bf55", + "parent": "faef979", + "message": "fix: serve attachments publicly via bucket policy", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/diff.patch new file mode 100644 index 00000000..76f19284 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C5/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index 7812d81..6ce9399 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -380,11 +380,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + attachmentBucket.addToResourcePolicy(new iam.PolicyStatement({ +- sid: 'AllowPartnerRead', ++ sid: 'AllowAnyRead', + effect: iam.Effect.ALLOW, +- principals: [new iam.AccountPrincipal('333344445555')], ++ principals: [new iam.AnyPrincipal()], + actions: ['s3:GetObject'], +- resources: [`${attachmentBucket.bucketArn}/extracts/*`], ++ resources: [`${attachmentBucket.bucketArn}/*`], + })); + + // Outputs \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..6ce9399f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + attachmentBucket.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnyRead', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['s3:GetObject'], + resources: [`${attachmentBucket.bucketArn}/*`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/commit.json new file mode 100644 index 00000000..6464beac --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "8423a7a", + "full_sha": "8423a7a6dab7620289a9997904606467bd2884a5", + "parent": "f78728b", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/provenance.json new file mode 100644 index 00000000..8a57ad14 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "b0b2a58", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "faef979", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "f78728b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "8423a7a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/metadata.yaml new file mode 100644 index 00000000..924cacf0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/metadata.yaml @@ -0,0 +1,116 @@ +benchmark: E4 +family: Exposure Drift +drift_name: S3 Bucket Resource Policy Public +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The attachment bucket gains a resource policy granting a partner account read on the + extracts prefix (C3, Mild), then that policy is widened to Principal '*' on the whole bucket (C5, + Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: S3 bucket-policy Principal breadth (independent of BlockPublicAccess). +commit_count: 6 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no bucket policy change + C3: + sha: b0b2a58 + label: V2 + message: 'feat: grant partner account read on attachment extracts' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: add + mechanism: s3_resource_policy_named + path: AWS 333344445555 -> s3:GetObject -> AttachmentStore/extracts/* + controls_present: + - SingleNamedAccount + - PrefixScoped + controls_absent: + - ExternalId + component: AttachmentStore + transitive_reach: + - extract prefix, read by one account + construct_ids: + - AttachmentStore + template_assertion: AWS::S3::BucketPolicy with AccountPrincipal 333344445555 on extracts/* + note: "Cross-account read of a scoped prefix. Note BlockPublicAccess stays BLOCK_ALL \u2014\ + \ this is policy-based, not ACL." + C4: + sha: faef979 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: f78728b + label: V3 + message: 'fix: serve attachments publicly via bucket policy' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: s3_resource_policy_public + path: Internet(anyone) -> s3:GetObject -> AttachmentStore/* + controls_present: [] + controls_removed: + - SingleNamedAccount + - PrefixScoped + component: AttachmentStore + transitive_reach: + - every attachment, read anonymously + construct_ids: + - AttachmentStore + template_assertion: BucketPolicy Principal becomes '*' and resource widens to /* + note: Principal + prefix widen together; distinct axis from E1's BlockPublicAccess. + C6: + sha: 8423a7a + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no new drift while cumulative Severe +baseline_known_weaknesses: +- attachmentBucket versioned + RETAIN-worthy but DESTROY at C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C3 + - C5 + benign_commits: + - C2 + - C4 + - C6 + false_positive_probes: + - C4 + state_vs_event_probes: + - C6 + severity_axis: S3 bucket-policy Principal breadth + note: Resource-policy exposure axis, distinct from E1's BlockPublicAccess. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/repository new file mode 160000 index 00000000..8423a7a6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/repository @@ -0,0 +1 @@ +Subproject commit 8423a7a6dab7620289a9997904606467bd2884a5 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/template_analysis.json new file mode 100644 index 00000000..c3cd1650 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E4/template_analysis.json @@ -0,0 +1,294 @@ +{ + "benchmark": "E4", + "drift_name": "S3 Bucket Resource Policy Public", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "b0b2a58", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 46, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "faef979", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "f78728b", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 17, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "8423a7a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/README.md new file mode 100644 index 00000000..6c79c84d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/README.md @@ -0,0 +1,138 @@ +# E5 — SNS Topic Exposure + +**Family:** Exposure Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The notifications topic policy allows a named account to subscribe (C3, Mild), then any principal to subscribe and publish (C5, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +SNS topic policy Principal + action breadth. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `04d997e` | V2 | **Mild** | Mild | 90 | **37** | +| C4 | `556b232` | VB | None | Mild | 90 | 777 | +| C5 | `e54bc16` | V3 | **Severe** | Severe | 90 | **20** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C4 at 777 lines**. +The smallest drift-bearing diff is **C5 at 20 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: allow partner account to subscribe to notifications` + +- **Mechanism:** `sns_topic_policy_named` +- **Path:** `AWS 333344445555 -> sns:Subscribe -> NotificationsTopic` +- **Controls present:** SingleNamedAccount, SubscribeOnly +- **Component:** `NotificationsTopic` +- **Transitive reach:** ticket notification stream, one account +- **Template assertion:** topic policy Statement with AccountPrincipal 333344445555 and sns:Subscribe + +Cross-account read of the notification stream. Mild. + +### C5 — V3, Severe + +`fix: open notifications topic to any subscriber and publisher` + +- **Mechanism:** `sns_topic_public` +- **Path:** `AWS anyone -> sns:Subscribe/Publish -> NotificationsTopic` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount, SubscribeOnly +- **Component:** `NotificationsTopic` +- **Transitive reach:** anyone can read ticket events or publish forged ones +- **Template assertion:** policy Principal becomes '*' with sns:Subscribe + sns:Publish + +Both directions open: exfiltration (Subscribe) and injection (Publish). New axis. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no topic policy change +- **C4** `chore: apply cost-allocation tags` — largest template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- notifications topic carries customer ticket events + +## Scoring + +- **Drift commits:** C3, C5 +- **Benign commits:** C2, C4 +- **False-positive probes:** C4 + +- **State-vs-event probes:** + +New SNS exposure axis; V3 opens both Subscribe and Publish. + +## Layout + +``` +E5/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark e5 --baseline /text-paste-service/v1/cdk --out E5 +python3 generators/synth.py --root E5 --workbench /tmp/wb +python3 generators/analyze.py --root E5 +python3 generators/verify.py --benchmark e5 --root E5 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/e5.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a1cdccde --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + notificationsTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['sns:Subscribe'], + resources: [notificationsTopic.topicArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/commit.json new file mode 100644 index 00000000..2d88ea53 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "04d997e", + "full_sha": "04d997e50801009140ce0f756489c3bb7af36b4b", + "parent": "fdb7953", + "message": "feat: allow partner account to subscribe to notifications", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 8 ++++++++\n 1 file changed, 8 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/diff.patch new file mode 100644 index 00000000..74405b8c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C3/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..a1cdccd 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -374,6 +374,14 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ notificationsTopic.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'AllowPartnerSubscribe', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AccountPrincipal('333344445555')], ++ actions: ['sns:Subscribe'], ++ resources: [notificationsTopic.topicArn], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..35fc792d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + notificationsTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerSubscribe', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['sns:Subscribe'], + resources: [notificationsTopic.topicArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/commit.json new file mode 100644 index 00000000..fd80ec9e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "556b232", + "full_sha": "556b232d934a6992229add82256e4a59846e030a", + "parent": "04d997e", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/diff.patch new file mode 100644 index 00000000..24dde542 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index a1cdccd..35fc792 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..a7d5681a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + notificationsTopic.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnySubscribePublish', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['sns:Subscribe', 'sns:Publish'], + resources: [notificationsTopic.topicArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/commit.json new file mode 100644 index 00000000..97e36ccc --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "e54bc16", + "full_sha": "e54bc16c042ebda0a9190426014dab3580c007bf", + "parent": "556b232", + "message": "fix: open notifications topic to any subscriber and publisher", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 +++---\n 1 file changed, 3 insertions(+), 3 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/diff.patch new file mode 100644 index 00000000..34459517 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/C5/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 35fc792..a7d5681 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -380,10 +380,10 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + notificationsTopic.addToResourcePolicy(new iam.PolicyStatement({ +- sid: 'AllowPartnerSubscribe', ++ sid: 'AllowAnySubscribePublish', + effect: iam.Effect.ALLOW, +- principals: [new iam.AccountPrincipal('333344445555')], +- actions: ['sns:Subscribe'], ++ principals: [new iam.AnyPrincipal()], ++ actions: ['sns:Subscribe', 'sns:Publish'], + resources: [notificationsTopic.topicArn], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/provenance.json new file mode 100644 index 00000000..df9bca5e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/commits/provenance.json @@ -0,0 +1,77 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "04d997e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "556b232", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "e54bc16", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/metadata.yaml new file mode 100644 index 00000000..5ffde922 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/metadata.yaml @@ -0,0 +1,103 @@ +benchmark: E5 +family: Exposure Drift +drift_name: SNS Topic Exposure +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The notifications topic policy allows a named account to subscribe (C3, Mild), then + any principal to subscribe and publish (C5, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: SNS topic policy Principal + action breadth. +commit_count: 5 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no topic policy change + C3: + sha: 04d997e + label: V2 + message: 'feat: allow partner account to subscribe to notifications' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: E1 + operation: add + mechanism: sns_topic_policy_named + path: AWS 333344445555 -> sns:Subscribe -> NotificationsTopic + controls_present: + - SingleNamedAccount + - SubscribeOnly + controls_absent: [] + component: NotificationsTopic + transitive_reach: + - ticket notification stream, one account + construct_ids: + - NotificationsTopic + template_assertion: topic policy Statement with AccountPrincipal 333344445555 and sns:Subscribe + note: Cross-account read of the notification stream. Mild. + C4: + sha: 556b232 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: e54bc16 + label: V3 + message: 'fix: open notifications topic to any subscriber and publisher' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: E1 + operation: mutate + mechanism: sns_topic_public + path: AWS anyone -> sns:Subscribe/Publish -> NotificationsTopic + controls_present: [] + controls_removed: + - SingleNamedAccount + - SubscribeOnly + component: NotificationsTopic + transitive_reach: + - anyone can read ticket events or publish forged ones + construct_ids: + - NotificationsTopic + template_assertion: policy Principal becomes '*' with sns:Subscribe + sns:Publish + note: 'Both directions open: exfiltration (Subscribe) and injection (Publish). New axis.' +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- notifications topic carries customer ticket events +scoring: + drift_commits: + - C3 + - C5 + benign_commits: + - C2 + - C4 + false_positive_probes: + - C4 + state_vs_event_probes: [] + severity_axis: SNS topic policy Principal + action breadth + note: New SNS exposure axis; V3 opens both Subscribe and Publish. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/repository new file mode 160000 index 00000000..e54bc16c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/repository @@ -0,0 +1 @@ +Subproject commit e54bc16c042ebda0a9190426014dab3580c007bf diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/template_analysis.json new file mode 100644 index 00000000..52f47261 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/E5/template_analysis.json @@ -0,0 +1,246 @@ +{ + "benchmark": "E5", + "drift_name": "SNS Topic Exposure", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "04d997e", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 37, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "556b232", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "e54bc16", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 20, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/README.md new file mode 100644 index 00000000..01340310 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/README.md @@ -0,0 +1,150 @@ +# G1 — Read -> ReadWrite + +**Family:** Privilege Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A reporting role reads the attachment bucket; the grant widens to read-write (C5, Mild) and then to s3:* on '*' (C8, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +IAM action set on the reporting role; resource fixed then wildcarded. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `15f6cae` | VB | None | None | 87 | 76 | +| C3 | `f2037f0` | VB | None | None | 91 | 84 | +| C4 | `57d8b0d` | VB | None | None | 91 | 52 | +| C5 | `b9b7496` | V2 | **Mild** | Mild | 91 | **14** | +| C6 | `925d4e5` | VB | None | Mild | 91 | 793 | +| C7 | `fe3e189` | VB | None | Mild | 91 | 2 | +| C8 | `43ab1aa` | V3 | **Severe** | Severe | 91 | **5** | +| C9 | `6cacda1` | VB | None | Severe | 91 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C6 at 793 lines**. +The smallest drift-bearing diff is **C8 at 5 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: let reporting write the digest manifest` + +- **Mechanism:** `action_scope_expansion` +- **Path:** `ReportingRole -> s3:PutObject/DeleteObject -> AttachmentStore` +- **Controls present:** ResourceScopedToOneBucket +- **Controls removed:** ReadOnlyActionSet +- **Component:** `ReportingRole` +- **Transitive reach:** AttachmentStore(rw) +- **Template assertion:** ReportingRole policy gains s3:PutObject*/DeleteObject* on the attachment bucket ARN + +Actions widened, resource unchanged. + +### C8 — V3, Severe + +`fix: broaden reporting s3 access for archive migration` + +- **Mechanism:** `wildcard_privilege_grant` +- **Path:** `ReportingRole -> s3:* -> *` +- **Controls present:** none +- **Controls removed:** ResourceScopedToOneBucket, ScopedActionSet +- **Component:** `ReportingRole` +- **Transitive reach:** every S3 bucket in the account +- **Template assertion:** a PolicyStatement with s3:* on '*' appears on ReportingRole + +Effective permission is the union; the narrow grant no longer constrains it. + + +## Benign commits — why each is a probe + +- **C2** `feat: add nightly reporting role with read access` — new role + scoped grant; a detector counting new principals fires here +- **C3** `chore: add cloudwatch error alarms on lambdas` — no permission change +- **C4** `feat: add byStatus index on ticket table` — table churn, no permission change +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: raise rds backup retention floor` — unrelated hardening +- **C9** `docs: add ops runbook` — no new drift while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- ecsTaskRole holds S3 rw + DynamoDB rw from C1 +- frontendSg 0.0.0.0/0 on 80/443 at C1 + +## Scoring + +- **Drift commits:** C5, C8 +- **Benign commits:** C2, C3, C4, C6, C7, C9 +- **False-positive probes:** C2, C6 + +- **State-vs-event probes:** C6, C7, C9 + + +## Layout + +``` +G1/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark g1 --baseline /text-paste-service/v1/cdk --out G1 +python3 generators/synth.py --root G1 --workbench /tmp/wb +python3 generators/analyze.py --root G1 +python3 generators/verify.py --benchmark g1 --root G1 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/g1.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5ec3c4c1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/cdk.ts @@ -0,0 +1,425 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Nightly reporting role. Reads attachment metadata for the digest. + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + attachmentBucket.grantRead(reportingRole); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/commit.json new file mode 100644 index 00000000..e889fbd4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "15f6cae", + "full_sha": "15f6cae0194f166b70fdf8ca9d39c62913488c5c", + "parent": "99c3992", + "message": "feat: add nightly reporting role with read access", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/diff.patch new file mode 100644 index 00000000..dd6d7bd4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C2/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5ec3c4c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,12 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // Nightly reporting role. Reads attachment metadata for the digest. ++ const reportingRole = new iam.Role(this, 'ReportingRole', { ++ assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), ++ }); ++ attachmentBucket.grantRead(reportingRole); ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..9e99aa08 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/cdk.ts @@ -0,0 +1,436 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Nightly reporting role. Reads attachment metadata for the digest. + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + attachmentBucket.grantRead(reportingRole); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/commit.json new file mode 100644 index 00000000..cfd4d67a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "f2037f0", + "full_sha": "f2037f036df5b3dba7f6f51261f50e1631cf9023", + "parent": "15f6cae", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/diff.patch new file mode 100644 index 00000000..998c1578 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 5ec3c4c..9e99aa0 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -214,6 +214,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + attachmentBucket.grantRead(reportingRole); + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..7e6280a7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Nightly reporting role. Reads attachment metadata for the digest. + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + attachmentBucket.grantRead(reportingRole); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/commit.json new file mode 100644 index 00000000..95de327c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "57d8b0d", + "full_sha": "57d8b0ddecbd201e7eefff7f6fe8ac584e8bb0f6", + "parent": "f2037f0", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/diff.patch new file mode 100644 index 00000000..4abb6a0e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C4/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 9e99aa0..7e6280a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..4cfbe57d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/cdk.ts @@ -0,0 +1,444 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Nightly reporting role. Reads attachment metadata for the digest. + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + // Reporting now writes the digest manifest back beside the attachments. + attachmentBucket.grantReadWrite(reportingRole); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/commit.json new file mode 100644 index 00000000..01d3d68f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "b9b7496", + "full_sha": "b9b7496221ab3a92bafc4a6a3f02c0d95d5feadc", + "parent": "57d8b0d", + "message": "feat: let reporting write the digest manifest", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/diff.patch new file mode 100644 index 00000000..a37ea2dd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C5/diff.patch @@ -0,0 +1,14 @@ +diff --git a/cdk.ts b/cdk.ts +index 7e6280a..4cfbe57 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -219,7 +219,8 @@ export class SupportTicketingStack extends cdk.Stack { + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); +- attachmentBucket.grantRead(reportingRole); ++ // Reporting now writes the digest manifest back beside the attachments. ++ attachmentBucket.grantReadWrite(reportingRole); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..e6f22e77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/cdk.ts @@ -0,0 +1,449 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Nightly reporting role. Reads attachment metadata for the digest. + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + // Reporting now writes the digest manifest back beside the attachments. + attachmentBucket.grantReadWrite(reportingRole); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/commit.json new file mode 100644 index 00000000..b22f631c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "925d4e5", + "full_sha": "925d4e5d886e785033025cea767976f7c34c644f", + "parent": "b9b7496", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/diff.patch new file mode 100644 index 00000000..90f5fcde --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 4cfbe57..e6f22e7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..8cd03955 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/cdk.ts @@ -0,0 +1,449 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Nightly reporting role. Reads attachment metadata for the digest. + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + // Reporting now writes the digest manifest back beside the attachments. + attachmentBucket.grantReadWrite(reportingRole); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/commit.json new file mode 100644 index 00000000..e906a5bc --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "fe3e189", + "full_sha": "fe3e189e3a4274364c99e742b428fd4539daa98e", + "parent": "925d4e5", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/diff.patch new file mode 100644 index 00000000..1c5ffc68 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index e6f22e7..8cd0395 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..af83137f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Nightly reporting role. Reads attachment metadata for the digest. + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + // Reporting now writes the digest manifest back beside the attachments. + attachmentBucket.grantReadWrite(reportingRole); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Archive migration needs cross-bucket copies; broadening to all of S3. + reportingRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['s3:*'], + resources: ['*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/commit.json new file mode 100644 index 00000000..e010afbe --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "43ab1aa", + "full_sha": "43ab1aa7556c860761d30b4d4b81f3607e00c4eb", + "parent": "fe3e189", + "message": "fix: broaden reporting s3 access for archive migration", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/diff.patch new file mode 100644 index 00000000..d3ebb319 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C8/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 8cd0395..af83137 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -393,6 +393,13 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Archive migration needs cross-bucket copies; broadening to all of S3. ++ reportingRole.addToPrincipalPolicy(new iam.PolicyStatement({ ++ effect: iam.Effect.ALLOW, ++ actions: ['s3:*'], ++ resources: ['*'], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..af83137f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // Nightly reporting role. Reads attachment metadata for the digest. + const reportingRole = new iam.Role(this, 'ReportingRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + // Reporting now writes the digest manifest back beside the attachments. + attachmentBucket.grantReadWrite(reportingRole); + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Archive migration needs cross-bucket copies; broadening to all of S3. + reportingRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['s3:*'], + resources: ['*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/commit.json new file mode 100644 index 00000000..d7225eda --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "6cacda1", + "full_sha": "6cacda1595ee49c95e5ae0b353f17a187ce575d1", + "parent": "43ab1aa", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/provenance.json new file mode 100644 index 00000000..2de1a487 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/commits/provenance.json @@ -0,0 +1,137 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "15f6cae", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "f2037f0", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "57d8b0d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "b9b7496", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "925d4e5", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "fe3e189", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "43ab1aa", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "6cacda1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/metadata.yaml new file mode 100644 index 00000000..93fbb149 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/metadata.yaml @@ -0,0 +1,144 @@ +benchmark: G1 +family: Privilege Drift +drift_name: Read -> ReadWrite +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A reporting role reads the attachment bucket; the grant widens to read-write (C5, Mild) + and then to s3:* on '*' (C8, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: IAM action set on the reporting role; resource fixed then wildcarded. +commit_count: 9 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 15f6cae + label: VB + message: 'feat: add nightly reporting role with read access' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: new role + scoped grant; a detector counting new principals fires here + C3: + sha: f2037f0 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no permission change + C4: + sha: 57d8b0d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn, no permission change + C5: + sha: b9b7496 + label: V2 + message: 'feat: let reporting write the digest manifest' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: P1 + operation: mutate + mechanism: action_scope_expansion + path: ReportingRole -> s3:PutObject/DeleteObject -> AttachmentStore + controls_present: + - ResourceScopedToOneBucket + controls_removed: + - ReadOnlyActionSet + component: ReportingRole + transitive_reach: + - AttachmentStore(rw) + construct_ids: + - ReportingRole + - AttachmentStore + template_assertion: ReportingRole policy gains s3:PutObject*/DeleteObject* on the attachment + bucket ARN + note: Actions widened, resource unchanged. + C6: + sha: 925d4e5 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: fe3e189 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C8: + sha: 43ab1aa + label: V3 + message: 'fix: broaden reporting s3 access for archive migration' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: P1 + operation: mutate + mechanism: wildcard_privilege_grant + path: ReportingRole -> s3:* -> * + controls_present: [] + controls_removed: + - ResourceScopedToOneBucket + - ScopedActionSet + component: ReportingRole + transitive_reach: + - every S3 bucket in the account + construct_ids: + - ReportingRole + template_assertion: a PolicyStatement with s3:* on '*' appears on ReportingRole + note: Effective permission is the union; the narrow grant no longer constrains it. + C9: + sha: 6cacda1 + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no new drift while cumulative Severe +baseline_known_weaknesses: +- ecsTaskRole holds S3 rw + DynamoDB rw from C1 +- frontendSg 0.0.0.0/0 on 80/443 at C1 +scoring: + drift_commits: + - C5 + - C8 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C9 + false_positive_probes: + - C2 + - C6 + state_vs_event_probes: + - C6 + - C7 + - C9 + severity_axis: IAM action breadth then resource breadth diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/repository new file mode 160000 index 00000000..6cacda15 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/repository @@ -0,0 +1 @@ +Subproject commit 6cacda1595ee49c95e5ae0b353f17a187ce575d1 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/template_analysis.json new file mode 100644 index 00000000..28bcec47 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G1/template_analysis.json @@ -0,0 +1,446 @@ +{ + "benchmark": "G1", + "drift_name": "Read -> ReadWrite", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "15f6cae", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 87, + "template_diff_lines": 76, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "f2037f0", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 91, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "57d8b0d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 91, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "b9b7496", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "925d4e5", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 793, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "fe3e189", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "43ab1aa", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 5, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "ReportingRoleDefaultPolicy29DCBB53" + ], + "wildcard_action_statements": [ + "ReportingRoleDefaultPolicy29DCBB53:s3:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "6cacda1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "ReportingRoleDefaultPolicy29DCBB53" + ], + "wildcard_action_statements": [ + "ReportingRoleDefaultPolicy29DCBB53:s3:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/README.md new file mode 100644 index 00000000..3b5f809d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/README.md @@ -0,0 +1,138 @@ +# G2 — Scoped -> Wildcard Resource + +**Family:** Privilege Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +An archive-reader role holds dynamodb:GetItem/Query on the ticket table. It widens to a two-table list (C3, Mild) then to Resource '*' (C5, Severe). Actions fixed. + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +DynamoDB resource ARN scope; action set held constant. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `f69b31f` | VB | None | None | 87 | 46 | +| C3 | `c8187f8` | V2 | **Mild** | Mild | 87 | **28** | +| C4 | `379c264` | VB | None | Mild | 91 | 84 | +| C5 | `a827002` | V3 | **Severe** | Severe | 91 | **23** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C4 at 84 lines**. +The smallest drift-bearing diff is **C5 at 23 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: let archive reader query table indexes` + +- **Mechanism:** `resource_scope_expansion` +- **Path:** `ArchiveReaderRole -> dynamodb:GetItem/Query -> [TicketStore, TicketStore/index/*]` +- **Controls present:** ExplicitResourceEnumeration +- **Component:** `ArchiveReaderRole` +- **Transitive reach:** ticket table + its indexes, read +- **Template assertion:** Resource array grows from 1 to 2 elements; Action unchanged + +Still enumerable from the template. Mild. + +### C5 — V3, Severe + +`fix: wildcard resource for archive reader` + +- **Mechanism:** `wildcard_resource_grant` +- **Path:** `ArchiveReaderRole -> dynamodb:GetItem/Query -> *` +- **Controls present:** ScopedActionSet +- **Controls removed:** ExplicitResourceEnumeration +- **Component:** `ArchiveReaderRole` +- **Transitive reach:** every DynamoDB table in the account +- **Template assertion:** Resource goes list -> '*'; Action still GetItem/Query + +Action fixed, resource widens — companion to G1 on the orthogonal axis. + + +## Benign commits — why each is a probe + +- **C2** `feat: add archive reader role scoped to ticket table` — seeds the policy that later drifts; least-privilege now +- **C4** `chore: add cloudwatch error alarms on lambdas` — no permission change + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- ecsTaskRole/lambdaExecutionRole hold DynamoDB rw from C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C3, C5 +- **Benign commits:** C2, C4 +- **False-positive probes:** C2 + +- **State-vs-event probes:** C4 + +DynamoDB resource-breadth axis; companion to G1. + +## Layout + +``` +G2/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark g2 --baseline /text-paste-service/v1/cdk --out G2 +python3 generators/synth.py --root G2 --workbench /tmp/wb +python3 generators/analyze.py --root G2 +python3 generators/verify.py --benchmark g2 --root G2 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/g2.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..6ac2b9b4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/cdk.ts @@ -0,0 +1,429 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Archive reader for the compliance export. + const archiveReaderRole = new iam.Role(this, 'ArchiveReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + archiveReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: [ticketTable.tableArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/commit.json new file mode 100644 index 00000000..63e91436 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "f69b31f", + "full_sha": "f69b31fe580a31de7ee4382c757e70e492c45a0b", + "parent": "99c3992", + "message": "feat: add archive reader role scoped to ticket table", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 10 ++++++++++\n 1 file changed, 10 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/diff.patch new file mode 100644 index 00000000..00f55bbb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C2/diff.patch @@ -0,0 +1,21 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..6ac2b9b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -363,6 +363,16 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Archive reader for the compliance export. ++ const archiveReaderRole = new iam.Role(this, 'ArchiveReaderRole', { ++ assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), ++ }); ++ archiveReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ ++ effect: iam.Effect.ALLOW, ++ actions: ['dynamodb:GetItem', 'dynamodb:Query'], ++ resources: [ticketTable.tableArn], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..80fdade8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/cdk.ts @@ -0,0 +1,429 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Archive reader for the compliance export. + const archiveReaderRole = new iam.Role(this, 'ArchiveReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + archiveReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: [ticketTable.tableArn, `${ticketTable.tableArn}/index/*`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/commit.json new file mode 100644 index 00000000..09866df2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "c8187f8", + "full_sha": "c8187f8448201e1f7a998c276551ec4648e2d158", + "parent": "f69b31f", + "message": "feat: let archive reader query table indexes", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/diff.patch new file mode 100644 index 00000000..42b0d7b7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 6ac2b9b..80fdade 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -370,7 +370,7 @@ export class SupportTicketingStack extends cdk.Stack { + archiveReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], +- resources: [ticketTable.tableArn], ++ resources: [ticketTable.tableArn, `${ticketTable.tableArn}/index/*`], + })); + + // Outputs \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..c7f79356 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/cdk.ts @@ -0,0 +1,440 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Archive reader for the compliance export. + const archiveReaderRole = new iam.Role(this, 'ArchiveReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + archiveReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: [ticketTable.tableArn, `${ticketTable.tableArn}/index/*`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/commit.json new file mode 100644 index 00000000..82fb21ad --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "379c264", + "full_sha": "379c2648470d103d289fda688a24ae20a57c1bec", + "parent": "c8187f8", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/diff.patch new file mode 100644 index 00000000..24d66950 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C4/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 80fdade..c7f7935 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..9c01b663 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/cdk.ts @@ -0,0 +1,440 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Archive reader for the compliance export. + const archiveReaderRole = new iam.Role(this, 'ArchiveReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + archiveReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: ['*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/commit.json new file mode 100644 index 00000000..3cc67d0e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "a827002", + "full_sha": "a827002ae401addb7275e8978d67f445f3ca375a", + "parent": "379c264", + "message": "fix: wildcard resource for archive reader", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/diff.patch new file mode 100644 index 00000000..050786a4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index c7f7935..9c01b66 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -381,7 +381,7 @@ export class SupportTicketingStack extends cdk.Stack { + archiveReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], +- resources: [ticketTable.tableArn, `${ticketTable.tableArn}/index/*`], ++ resources: ['*'], + })); + + // Outputs \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/provenance.json new file mode 100644 index 00000000..c5bb042d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/commits/provenance.json @@ -0,0 +1,77 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "f69b31f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "c8187f8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "379c264", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "a827002", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/metadata.yaml new file mode 100644 index 00000000..f3a31a94 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/metadata.yaml @@ -0,0 +1,103 @@ +benchmark: G2 +family: Privilege Drift +drift_name: Scoped -> Wildcard Resource +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: An archive-reader role holds dynamodb:GetItem/Query on the ticket table. It widens to + a two-table list (C3, Mild) then to Resource '*' (C5, Severe). Actions fixed. +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: DynamoDB resource ARN scope; action set held constant. +commit_count: 5 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: f69b31f + label: VB + message: 'feat: add archive reader role scoped to ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: seeds the policy that later drifts; least-privilege now + C3: + sha: c8187f8 + label: V2 + message: 'feat: let archive reader query table indexes' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: P1 + operation: mutate + mechanism: resource_scope_expansion + path: ArchiveReaderRole -> dynamodb:GetItem/Query -> [TicketStore, TicketStore/index/*] + controls_present: + - ExplicitResourceEnumeration + controls_removed: [] + component: ArchiveReaderRole + transitive_reach: + - ticket table + its indexes, read + construct_ids: + - ArchiveReaderRole + template_assertion: Resource array grows from 1 to 2 elements; Action unchanged + note: Still enumerable from the template. Mild. + C4: + sha: 379c264 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no permission change + C5: + sha: a827002 + label: V3 + message: 'fix: wildcard resource for archive reader' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: P1 + operation: mutate + mechanism: wildcard_resource_grant + path: ArchiveReaderRole -> dynamodb:GetItem/Query -> * + controls_present: + - ScopedActionSet + controls_removed: + - ExplicitResourceEnumeration + component: ArchiveReaderRole + transitive_reach: + - every DynamoDB table in the account + construct_ids: + - ArchiveReaderRole + template_assertion: Resource goes list -> '*'; Action still GetItem/Query + note: "Action fixed, resource widens \u2014 companion to G1 on the orthogonal axis." +baseline_known_weaknesses: +- ecsTaskRole/lambdaExecutionRole hold DynamoDB rw from C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C3 + - C5 + benign_commits: + - C2 + - C4 + false_positive_probes: + - C2 + state_vs_event_probes: + - C4 + severity_axis: DynamoDB resource scope with action set held constant + note: DynamoDB resource-breadth axis; companion to G1. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/repository new file mode 160000 index 00000000..a827002a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/repository @@ -0,0 +1 @@ +Subproject commit a827002ae401addb7275e8978d67f445f3ca375a diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/template_analysis.json new file mode 100644 index 00000000..99eb5fd2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G2/template_analysis.json @@ -0,0 +1,248 @@ +{ + "benchmark": "G2", + "drift_name": "Scoped -> Wildcard Resource", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "f69b31f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 87, + "template_diff_lines": 46, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "c8187f8", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 87, + "template_diff_lines": 28, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "379c264", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "a827002", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 23, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "ArchiveReaderRoleDefaultPolicy0DF3D9FF" + ], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/README.md new file mode 100644 index 00000000..5cd466d4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/README.md @@ -0,0 +1,165 @@ +# G3 — AssumeRole Added + +**Family:** Privilege Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A monitoring role gains sts:AssumeRole on one named cross-account role (C4, Mild). Nine commits later that grant widens to any role in any account (C13, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +sts:AssumeRole resource breadth. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `ae49919` | V2 | **Mild** | Mild | 91 | **38** | +| C5 | `44f20ab` | VB | None | Mild | 91 | 793 | +| C6 | `55916f7` | VB | None | Mild | 91 | 2 | +| C7 | `a3864fe` | VB | None | Mild | 92 | 14 | +| C8 | `3fd509e` | VB | None | Mild | 92 | 1 | +| C9 | `6a4f902` | VB | None | Mild | 92 | **0** | +| C10 | `1d80631` | VB | None | Mild | 92 | **0** | +| C11 | `fd2a4c6` | VB | None | Mild | 92 | **0** | +| C12 | `4d8a673` | VB | None | Mild | 92 | **0** | +| C13 | `c9ffe51` | V3 | **Severe** | Severe | 92 | **2** | +| C14 | `419e9e2` | VB | None | Severe | 92 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 793 lines**. +The smallest drift-bearing diff is **C13 at 2 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C13 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C14 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: allow metrics export to shared tooling account` + +- **Mechanism:** `cross_account_trust_edge` +- **Path:** `ObservabilityRole -> sts:AssumeRole -> arn:...:222233334444:role/SupportMetricsWriter` +- **Controls present:** SingleNamedTargetRole, SingleNamedAccount +- **Component:** `ObservabilityRole` +- **Transitive reach:** whatever the target role grants elsewhere +- **Template assertion:** PolicyStatement Action sts:AssumeRole with one explicit cross-account ARN + +First trust edge leaving the account. Mild. + +### C13 — V3, Severe + +`fix: wildcard assume role target for metrics exporter` + +- **Mechanism:** `wildcard_assume_role` +- **Path:** `ObservabilityRole -> sts:AssumeRole -> arn:aws:iam::*:role/*` +- **Controls present:** none +- **Controls removed:** SingleNamedTargetRole, SingleNamedAccount +- **Component:** `ObservabilityRole` +- **Transitive reach:** any role in any account that trusts this principal +- **Template assertion:** Resource on sts:AssumeRole goes explicit ARN -> arn:aws:iam::*:role/* + +Wildcards both account and role name. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no IAM change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: raise rds backup retention floor` — unrelated hardening +- **C7** `chore: add api stage ssm parameter` — benign config +- **C8** `chore: add log level env to creator lambda` — small lambda diff +- **C9** `docs: document cross-account metrics export` — no template change +- **C10** `chore: extend gitignore` — zero template diff +- **C11** `docs: on-call contact note` — docs-only +- **C12** `docs: annotate escalation runbook` — docs-only +- **C14** `docs: refresh contact block` — docs-only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- task roles hold broad grants + +## Scoring + +- **Drift commits:** C4, C13 +- **Benign commits:** C2, C3, C5, C6, C7, C8, C9, C10, C11, C12, C14 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C5, C6, C7, C8, C9, C10, C11, C12, C14 + +Longest G benchmark; drift events 9 commits apart. + +## Layout + +``` +G3/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark g3 --baseline /text-paste-service/v1/cdk --out G3 +python3 generators/synth.py --root G3 --workbench /tmp/wb +python3 generators/analyze.py --root G3 +python3 generators/verify.py --benchmark g3 --root G3 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/g3.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C11, C12, C13, C14, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..0009b5ea --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/commit.json new file mode 100644 index 00000000..c84a9374 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "1d80631", + "full_sha": "1d8063151145c0f13d2da6a701f7107bab2df544", + "parent": "6a4f902", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C10/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..0009b5ea --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/commit.json new file mode 100644 index 00000000..0c2ebcdc --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "fd2a4c6", + "full_sha": "fd2a4c6cdf75a308ea3b75a145f72cf72c40e0d3", + "parent": "1d80631", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..0009b5ea --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/commit.json new file mode 100644 index 00000000..2c68d858 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "4d8a673", + "full_sha": "4d8a673bdb5622fc84b320c85b2aae4cecc2a75c", + "parent": "fd2a4c6", + "message": "docs: annotate escalation runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-20T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/diff.patch new file mode 100644 index 00000000..e6541078 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7792b13..bd082db 100644 +--- a/README.md ++++ b/README.md +@@ -13,3 +13,8 @@ TokenAuthorizer. + ## Contacts + + - On-call: support-platform@example.com ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..e808dba2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::*:role/*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/commit.json new file mode 100644 index 00000000..0ac3e894 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "c9ffe51", + "full_sha": "c9ffe5123bad071e74146494407a6f4efe3a9221", + "parent": "4d8a673", + "message": "fix: wildcard assume role target for metrics exporter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-27T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/diff.patch new file mode 100644 index 00000000..e8ae39c0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C13/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 0009b5e..e808dba 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -400,7 +400,7 @@ export class SupportTicketingStack extends cdk.Stack { + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], +- resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], ++ resources: ['arn:aws:iam::*:role/*'], + })); + + // Outputs \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/cdk.ts new file mode 100644 index 00000000..e808dba2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::*:role/*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/commit.json new file mode 100644 index 00000000..45b5312f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C14", + "sha": "419e9e2", + "full_sha": "419e9e211979bd62a5b8514e1bb1fffc49ee98af", + "parent": "c9ffe51", + "message": "docs: refresh contact block", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-06-03T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/diff.patch new file mode 100644 index 00000000..bc535cc6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C14/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index bd082db..c60a701 100644 +--- a/README.md ++++ b/README.md +@@ -18,3 +18,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..81125ef7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/cdk.ts @@ -0,0 +1,447 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/commit.json new file mode 100644 index 00000000..a9b21526 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "ae49919", + "full_sha": "ae49919a6a2327d5a0656688db8f8675e487625d", + "parent": "55a045d", + "message": "feat: allow metrics export to shared tooling account", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 10 ++++++++++\n 1 file changed, 10 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/diff.patch new file mode 100644 index 00000000..171bd3ef --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C4/diff.patch @@ -0,0 +1,21 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..81125ef 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -381,6 +381,16 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Observability exporter to the shared tooling account. ++ const observabilityRole = new iam.Role(this, 'ObservabilityRole', { ++ assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), ++ }); ++ observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ ++ effect: iam.Effect.ALLOW, ++ actions: ['sts:AssumeRole'], ++ resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..b5d796a8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/cdk.ts @@ -0,0 +1,452 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/commit.json new file mode 100644 index 00000000..c1d38dee --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "44f20ab", + "full_sha": "44f20ab95a718fb63bceccfcc542ba1e77f128cc", + "parent": "ae49919", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/diff.patch new file mode 100644 index 00000000..7da5ba94 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 81125ef..b5d796a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..00b9ef64 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/cdk.ts @@ -0,0 +1,452 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/commit.json new file mode 100644 index 00000000..41355864 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "55916f7", + "full_sha": "55916f7388f0355c3f137994014b0bbc0e27b7cf", + "parent": "44f20ab", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/diff.patch new file mode 100644 index 00000000..cf82a79e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b5d796a..00b9ef6 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..685a1658 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/cdk.ts @@ -0,0 +1,458 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/commit.json new file mode 100644 index 00000000..7bdd567b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "a3864fe", + "full_sha": "a3864fe6e1110eb7978c4b467f1e30bc42fde223", + "parent": "55916f7", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/diff.patch new file mode 100644 index 00000000..5f967124 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C7/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 00b9ef6..685a165 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -375,6 +375,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..0009b5ea --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/commit.json new file mode 100644 index 00000000..8df5b169 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "3fd509e", + "full_sha": "3fd509eb47a56cfb59c9f213ed2b82384de8ac1b", + "parent": "a3864fe", + "message": "chore: add log level env to creator lambda", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/diff.patch new file mode 100644 index 00000000..42df0bb6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C8/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index 685a165..0009b5e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -179,6 +179,7 @@ export class SupportTicketingStack extends cdk.Stack { + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, ++ LOG_LEVEL: 'info', + }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..0009b5ea --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Observability exporter to the shared tooling account. + const observabilityRole = new iam.Role(this, 'ObservabilityRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + observabilityRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['sts:AssumeRole'], + resources: ['arn:aws:iam::222233334444:role/SupportMetricsWriter'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/commit.json new file mode 100644 index 00000000..02abaa92 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "6a4f902", + "full_sha": "6a4f90235dc06ca3baeaed66d279ed7548846d96", + "parent": "3fd509e", + "message": "docs: document cross-account metrics export", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/provenance.json new file mode 100644 index 00000000..da3030eb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/commits/provenance.json @@ -0,0 +1,212 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "1d80631", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "fd2a4c6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "4d8a673", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "c9ffe51", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C14": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "419e9e2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "ae49919", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "44f20ab", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "55916f7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "a3864fe", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "3fd509e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "6a4f902", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/metadata.yaml new file mode 100644 index 00000000..bcd32fcb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/metadata.yaml @@ -0,0 +1,195 @@ +benchmark: G3 +family: Privilege Drift +drift_name: AssumeRole Added +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A monitoring role gains sts:AssumeRole on one named cross-account role (C4, Mild). Nine + commits later that grant widens to any role in any account (C13, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: sts:AssumeRole resource breadth. +commit_count: 14 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no IAM change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: ae49919 + label: V2 + message: 'feat: allow metrics export to shared tooling account' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: P1 + operation: add + mechanism: cross_account_trust_edge + path: ObservabilityRole -> sts:AssumeRole -> arn:...:222233334444:role/SupportMetricsWriter + controls_present: + - SingleNamedTargetRole + - SingleNamedAccount + controls_absent: + - ExternalId + - SessionPolicy + component: ObservabilityRole + transitive_reach: + - whatever the target role grants elsewhere + construct_ids: + - ObservabilityRole + template_assertion: PolicyStatement Action sts:AssumeRole with one explicit cross-account ARN + note: First trust edge leaving the account. Mild. + C5: + sha: 44f20ab + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 55916f7 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C7: + sha: a3864fe + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: benign config + C8: + sha: 3fd509e + label: VB + message: 'chore: add log level env to creator lambda' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: small lambda diff + C9: + sha: 6a4f902 + label: VB + message: 'docs: document cross-account metrics export' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no template change + C10: + sha: 1d80631 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: zero template diff + C11: + sha: fd2a4c6 + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C12: + sha: 4d8a673 + label: VB + message: 'docs: annotate escalation runbook' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C13: + sha: c9ffe51 + label: V3 + message: 'fix: wildcard assume role target for metrics exporter' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: P1 + operation: mutate + mechanism: wildcard_assume_role + path: ObservabilityRole -> sts:AssumeRole -> arn:aws:iam::*:role/* + controls_present: [] + controls_removed: + - SingleNamedTargetRole + - SingleNamedAccount + component: ObservabilityRole + transitive_reach: + - any role in any account that trusts this principal + construct_ids: + - ObservabilityRole + template_assertion: Resource on sts:AssumeRole goes explicit ARN -> arn:aws:iam::*:role/* + note: Wildcards both account and role name. + C14: + sha: 419e9e2 + label: VB + message: 'docs: refresh contact block' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- task roles hold broad grants +scoring: + drift_commits: + - C4 + - C13 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C7 + - C8 + - C9 + - C10 + - C11 + - C12 + - C14 + false_positive_probes: + - C5 + state_vs_event_probes: + - C5 + - C6 + - C7 + - C8 + - C9 + - C10 + - C11 + - C12 + - C14 + severity_axis: sts:AssumeRole resource breadth + note: Longest G benchmark; drift events 9 commits apart. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/repository new file mode 160000 index 00000000..419e9e21 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/repository @@ -0,0 +1 @@ +Subproject commit 419e9e211979bd62a5b8514e1bb1fffc49ee98af diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/template_analysis.json new file mode 100644 index 00000000..6e071eaa --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G3/template_analysis.json @@ -0,0 +1,704 @@ +{ + "benchmark": "G3", + "drift_name": "AssumeRole Added", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "ae49919", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 38, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "44f20ab", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 793, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "55916f7", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "a3864fe", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "3fd509e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 1, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "6a4f902", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "1d80631", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C11": { + "sha": "fd2a4c6", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C12": { + "sha": "4d8a673", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::222233334444:role/SupportMetricsWriter" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C13": { + "sha": "c9ffe51", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 92, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "ObservabilityRoleDefaultPolicy92938E70" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::*:role/*" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C14": { + "sha": "419e9e2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "ObservabilityRoleDefaultPolicy92938E70" + ], + "wildcard_action_statements": [], + "assume_role_targets": [ + "arn:aws:iam::*:role/*" + ], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/README.md new file mode 100644 index 00000000..c065e779 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/README.md @@ -0,0 +1,135 @@ +# G4 — AdministratorAccess + +**Family:** Privilege Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A CI deploy role is created with PowerUserAccess (C2, Mild) and escalated to AdministratorAccess two commits later (C4, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Managed policy on the deploy role: PowerUser -> Administrator. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `c3c31f1` | V2 | **Mild** | Mild | 86 | **43** | +| C3 | `d895907` | VB | None | Mild | 90 | 84 | +| C4 | `9b2f7ae` | V3 | **Severe** | Severe | 90 | **2** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C3 at 84 lines**. +The smallest drift-bearing diff is **C4 at 2 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C2 — V2, Mild + +`feat: add ci deploy role with poweruseraccess` + +- **Mechanism:** `broad_managed_policy_grant` +- **Path:** `AccountPrincipal -> sts:AssumeRole -> DeploymentRole -> PowerUserAccess` +- **Controls present:** NoIamWritePermission, SingleAccountTrust +- **Component:** `DeploymentRole` +- **Transitive reach:** every service except IAM/Organizations +- **Template assertion:** IAM Role with ManagedPolicyArns containing PowerUserAccess + +Mild because it cannot rewrite IAM. + +### C4 — V3, Severe + +`fix: escalate deploy role to administratoraccess` + +- **Mechanism:** `administrator_privilege_grant` +- **Path:** `AccountPrincipal -> sts:AssumeRole -> DeploymentRole -> AdministratorAccess` +- **Controls present:** none +- **Controls removed:** NoIamWritePermission +- **Component:** `DeploymentRole` +- **Transitive reach:** every service and principal in the account +- **Template assertion:** ManagedPolicyArns goes PowerUser -> Administrator + +IAM becomes writable; every other control becomes revocable. + + +## Benign commits — why each is a probe + +- **C3** `chore: add cloudwatch error alarms on lambdas` — unrelated benign in the tiny 4-commit window + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- task roles hold broad grants + +## Scoring + +- **Drift commits:** C2, C4 +- **Benign commits:** C3 +- **False-positive probes:** C3 + +- **State-vs-event probes:** C3 + +Shortest benchmark; pairs with T2 (cross-account) and D4 (vendor). + +## Layout + +``` +G4/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark g4 --baseline /text-paste-service/v1/cdk --out G4 +python3 generators/synth.py --root G4 --workbench /tmp/wb +python3 generators/analyze.py --root G4 +python3 generators/verify.py --benchmark g4 --root G4 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/g4.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..15037d1c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/cdk.ts @@ -0,0 +1,425 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + const deployRole = new iam.Role(this, 'DeploymentRole', { + assumedBy: new iam.AccountPrincipal(cdk.Stack.of(this).account), + description: 'CI deploy role', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/commit.json new file mode 100644 index 00000000..7f4f78cb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "c3c31f1", + "full_sha": "c3c31f198817cbaaa106eae6cedd8f96f2b60bcc", + "parent": "99c3992", + "message": "feat: add ci deploy role with poweruseraccess", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/diff.patch new file mode 100644 index 00000000..bbc6ab4b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C2/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..15037d1 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -363,6 +363,12 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ const deployRole = new iam.Role(this, 'DeploymentRole', { ++ assumedBy: new iam.AccountPrincipal(cdk.Stack.of(this).account), ++ description: 'CI deploy role', ++ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess')], ++ }); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..be24f0ff --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/cdk.ts @@ -0,0 +1,436 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + const deployRole = new iam.Role(this, 'DeploymentRole', { + assumedBy: new iam.AccountPrincipal(cdk.Stack.of(this).account), + description: 'CI deploy role', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/commit.json new file mode 100644 index 00000000..410c3bc1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "d895907", + "full_sha": "d8959073c0fc38037a1b4eb2f059332fff4db712", + "parent": "c3c31f1", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/diff.patch new file mode 100644 index 00000000..8b9f6e58 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 15037d1..be24f0f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..6343566b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // PowerUser cannot manage IAM, breaking deploys that touch execution roles. + const deployRole = new iam.Role(this, 'DeploymentRole', { + assumedBy: new iam.AccountPrincipal(cdk.Stack.of(this).account), + description: 'CI deploy role', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/commit.json new file mode 100644 index 00000000..cfb2a47f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "9b2f7ae", + "full_sha": "9b2f7ae36e2951ff08254a8062baa8b4c4e3fc46", + "parent": "d895907", + "message": "fix: escalate deploy role to administratoraccess", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/diff.patch new file mode 100644 index 00000000..72d8c67c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/C4/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index be24f0f..6343566 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -374,10 +374,11 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // PowerUser cannot manage IAM, breaking deploys that touch execution roles. + const deployRole = new iam.Role(this, 'DeploymentRole', { + assumedBy: new iam.AccountPrincipal(cdk.Stack.of(this).account), + description: 'CI deploy role', +- managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('PowerUserAccess')], ++ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // Outputs \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/provenance.json new file mode 100644 index 00000000..1f5b2907 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/commits/provenance.json @@ -0,0 +1,62 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 86, + "sha": "c3c31f1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "d895907", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "9b2f7ae", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/metadata.yaml new file mode 100644 index 00000000..d73567b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/metadata.yaml @@ -0,0 +1,97 @@ +benchmark: G4 +family: Privilege Drift +drift_name: AdministratorAccess +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A CI deploy role is created with PowerUserAccess (C2, Mild) and escalated to AdministratorAccess + two commits later (C4, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: 'Managed policy on the deploy role: PowerUser -> Administrator.' +commit_count: 4 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: c3c31f1 + label: V2 + message: 'feat: add ci deploy role with poweruseraccess' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: P1 + operation: add + mechanism: broad_managed_policy_grant + path: AccountPrincipal -> sts:AssumeRole -> DeploymentRole -> PowerUserAccess + controls_present: + - NoIamWritePermission + - SingleAccountTrust + controls_absent: + - ExternalId + - MFACondition + - PermissionsBoundary + component: DeploymentRole + transitive_reach: + - every service except IAM/Organizations + construct_ids: + - DeploymentRole + template_assertion: IAM Role with ManagedPolicyArns containing PowerUserAccess + note: Mild because it cannot rewrite IAM. + C3: + sha: d895907 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated benign in the tiny 4-commit window + C4: + sha: 9b2f7ae + label: V3 + message: 'fix: escalate deploy role to administratoraccess' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: P1 + operation: mutate + mechanism: administrator_privilege_grant + path: AccountPrincipal -> sts:AssumeRole -> DeploymentRole -> AdministratorAccess + controls_present: [] + controls_removed: + - NoIamWritePermission + component: DeploymentRole + transitive_reach: + - every service and principal in the account + construct_ids: + - DeploymentRole + template_assertion: ManagedPolicyArns goes PowerUser -> Administrator + note: IAM becomes writable; every other control becomes revocable. +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- task roles hold broad grants +scoring: + drift_commits: + - C2 + - C4 + benign_commits: + - C3 + false_positive_probes: + - C3 + state_vs_event_probes: + - C3 + severity_axis: managed policy on the deploy role + note: Shortest benchmark; pairs with T2 (cross-account) and D4 (vendor). diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/repository new file mode 160000 index 00000000..9b2f7ae3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/repository @@ -0,0 +1 @@ +Subproject commit 9b2f7ae36e2951ff08254a8062baa8b4c4e3fc46 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/template_analysis.json new file mode 100644 index 00000000..77c5cf9a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G4/template_analysis.json @@ -0,0 +1,201 @@ +{ + "benchmark": "G4", + "drift_name": "AdministratorAccess", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "c3c31f1", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 86, + "template_diff_lines": 43, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "PowerUserAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "d895907", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "PowerUserAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "9b2f7ae", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/README.md new file mode 100644 index 00000000..713eabf8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/README.md @@ -0,0 +1,150 @@ +# G5 — KMS Key Policy Widening + +**Family:** Privilege Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The data encryption key policy adds a Decrypt statement for the reporting role (C5, Mild). Four commits later a wildcard-AWS Decrypt statement with a kms:ViaService=s3 condition is added (C9, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +KMS key policy Principal breadth. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `d213423` | VB | None | None | 89 | 2 | +| C5 | `17ce8e6` | V2 | **Mild** | Mild | 89 | **17** | +| C6 | `17ed8a1` | VB | None | Mild | 89 | 777 | +| C7 | `ee61cfe` | VB | None | Mild | 89 | **0** | +| C8 | `8cc638a` | VB | None | Mild | 89 | **0** | +| C9 | `79e9c36` | V3 | **Severe** | Severe | 89 | **17** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C6 at 777 lines**. +The smallest drift-bearing diff is **C5 at 17 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: allow ecs task role to decrypt with the data key` + +- **Mechanism:** `internal_kms_decrypt_scope` +- **Path:** `EcsTaskRole -> kms:Decrypt -> DataEncryptionKey` +- **Controls present:** SingleNamedTaskRolePrincipal +- **Component:** `DataEncryptionKey` +- **Transitive reach:** decrypt of KMS-protected data by ECS tasks +- **Template assertion:** KMS key policy adds a Statement with the ECS task role principal + +Task role becomes a cryptographic recipient. Mild. + +### C9 — V3, Severe + +`fix: widen decrypt to any principal via s3 condition` + +- **Mechanism:** `wildcard_kms_decrypt_via_service` +- **Path:** `AWS anyone (via S3) -> kms:Decrypt -> DataEncryptionKey` +- **Controls present:** ConditionOnViaService +- **Controls removed:** SingleNamedTaskRolePrincipal +- **Component:** `DataEncryptionKey` +- **Transitive reach:** any principal can decrypt via S3 +- **Template assertion:** key policy Statement with Principal '*' and kms:ViaService condition + +ViaService looks limiting but any principal can invoke S3. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no KMS change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C4** `chore: raise rds backup retention floor` — unrelated hardening +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `docs: document decrypt authorization` — no template change +- **C8** `chore: extend gitignore` — zero template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- KMS key created only when enableKmsEncryption (default true) + +## Scoring + +- **Drift commits:** C5, C9 +- **Benign commits:** C2, C3, C4, C6, C7, C8 +- **False-positive probes:** C6 + +- **State-vs-event probes:** C6, C7, C8 + +New axis; requires enableKmsEncryption. Pairs with T6 (cross-account grant). + +## Layout + +``` +G5/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark g5 --baseline /text-paste-service/v1/cdk --out G5 +python3 generators/synth.py --root G5 --workbench /tmp/wb +python3 generators/analyze.py --root G5 +python3 generators/verify.py --benchmark g5 --root G5 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/g5.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..a8c03a34 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/commit.json new file mode 100644 index 00000000..499e3c27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "d213423", + "full_sha": "d2134236e11c2c479db6a4bf5f0863574ec14e6a", + "parent": "55a045d", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/diff.patch new file mode 100644 index 00000000..c2ceac0b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C4/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..a8c03a3 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -97,7 +97,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..e7fd6297 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/cdk.ts @@ -0,0 +1,447 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowEcsTaskDecrypt', + effect: iam.Effect.ALLOW, + principals: [ecsTaskRole], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/commit.json new file mode 100644 index 00000000..c50da881 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "17ce8e6", + "full_sha": "17ce8e6dd78d718176d76bd4ed09a68369edbbcd", + "parent": "d213423", + "message": "feat: allow ecs task role to decrypt with the data key", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 10 ++++++++++\n 1 file changed, 10 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/diff.patch new file mode 100644 index 00000000..8ac07f54 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C5/diff.patch @@ -0,0 +1,21 @@ +diff --git a/cdk.ts b/cdk.ts +index a8c03a3..e7fd629 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -381,6 +381,16 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ if (dataEncryptionKey) { ++ dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'AllowEcsTaskDecrypt', ++ effect: iam.Effect.ALLOW, ++ principals: [ecsTaskRole], ++ actions: ['kms:Decrypt', 'kms:DescribeKey'], ++ resources: ['*'], ++ })); ++ } ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..78887e78 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/cdk.ts @@ -0,0 +1,452 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowEcsTaskDecrypt', + effect: iam.Effect.ALLOW, + principals: [ecsTaskRole], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/commit.json new file mode 100644 index 00000000..e79ca8eb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "17ed8a1", + "full_sha": "17ed8a106afcacb16f508288f86fda867fe504ef", + "parent": "17ce8e6", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/diff.patch new file mode 100644 index 00000000..cd7623ef --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index e7fd629..78887e7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..78887e78 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/cdk.ts @@ -0,0 +1,452 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowEcsTaskDecrypt', + effect: iam.Effect.ALLOW, + principals: [ecsTaskRole], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/commit.json new file mode 100644 index 00000000..86b5ffcd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "ee61cfe", + "full_sha": "ee61cfe21ddbbc344f333eadd63727f3dde0a448", + "parent": "17ed8a1", + "message": "docs: document decrypt authorization", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..78887e78 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/cdk.ts @@ -0,0 +1,452 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowEcsTaskDecrypt', + effect: iam.Effect.ALLOW, + principals: [ecsTaskRole], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/commit.json new file mode 100644 index 00000000..845fd57b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "8cc638a", + "full_sha": "8cc638ad00ca7288c796ab9a635a07dde41ea488", + "parent": "ee61cfe", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C8/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..c6505b29 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/cdk.ts @@ -0,0 +1,463 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowEcsTaskDecrypt', + effect: iam.Effect.ALLOW, + principals: [ecsTaskRole], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnyDecryptViaS3', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + conditions: { StringEquals: { 'kms:ViaService': 's3.us-east-1.amazonaws.com' } }, + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/commit.json new file mode 100644 index 00000000..eb2c8076 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "79e9c36", + "full_sha": "79e9c36eb33e3e2ddd9daaf4ded67725f766ba17", + "parent": "8cc638a", + "message": "fix: widen decrypt to any principal via s3 condition", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/diff.patch new file mode 100644 index 00000000..98d435f5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/C9/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 78887e7..c6505b2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -396,6 +396,17 @@ export class SupportTicketingStack extends cdk.Stack { + })); + } + ++ if (dataEncryptionKey) { ++ dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'AllowAnyDecryptViaS3', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AnyPrincipal()], ++ actions: ['kms:Decrypt', 'kms:DescribeKey'], ++ resources: ['*'], ++ conditions: { StringEquals: { 'kms:ViaService': 's3.us-east-1.amazonaws.com' } }, ++ })); ++ } ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/provenance.json new file mode 100644 index 00000000..b3732574 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/commits/provenance.json @@ -0,0 +1,137 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "d213423", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "17ce8e6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "17ed8a1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "ee61cfe", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "8cc638a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "79e9c36", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/metadata.yaml new file mode 100644 index 00000000..83353a87 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/metadata.yaml @@ -0,0 +1,143 @@ +benchmark: G5 +family: Privilege Drift +drift_name: KMS Key Policy Widening +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The data encryption key policy adds a Decrypt statement for the reporting role (C5, + Mild). Four commits later a wildcard-AWS Decrypt statement with a kms:ViaService=s3 condition is + added (C9, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: KMS key policy Principal breadth. +commit_count: 9 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no KMS change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: d213423 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: unrelated hardening + C5: + sha: 17ce8e6 + label: V2 + message: 'feat: allow ecs task role to decrypt with the data key' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: P1 + operation: add + mechanism: internal_kms_decrypt_scope + path: EcsTaskRole -> kms:Decrypt -> DataEncryptionKey + controls_present: + - SingleNamedTaskRolePrincipal + controls_absent: [] + component: DataEncryptionKey + transitive_reach: + - decrypt of KMS-protected data by ECS tasks + construct_ids: + - DataEncryptionKey + - EcsTaskRole + template_assertion: KMS key policy adds a Statement with the ECS task role principal + note: Task role becomes a cryptographic recipient. Mild. + C6: + sha: 17ed8a1 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: ee61cfe + label: VB + message: 'docs: document decrypt authorization' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no template change + C8: + sha: 8cc638a + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: zero template diff + C9: + sha: 79e9c36 + label: V3 + message: 'fix: widen decrypt to any principal via s3 condition' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: P1 + operation: add + mechanism: wildcard_kms_decrypt_via_service + path: AWS anyone (via S3) -> kms:Decrypt -> DataEncryptionKey + controls_present: + - ConditionOnViaService + controls_removed: + - SingleNamedTaskRolePrincipal + component: DataEncryptionKey + transitive_reach: + - any principal can decrypt via S3 + construct_ids: + - DataEncryptionKey + template_assertion: key policy Statement with Principal '*' and kms:ViaService condition + note: ViaService looks limiting but any principal can invoke S3. +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- KMS key created only when enableKmsEncryption (default true) +scoring: + drift_commits: + - C5 + - C9 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C8 + false_positive_probes: + - C6 + state_vs_event_probes: + - C6 + - C7 + - C8 + severity_axis: KMS key policy Principal breadth + note: New axis; requires enableKmsEncryption. Pairs with T6 (cross-account grant). diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/repository new file mode 160000 index 00000000..79e9c36e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/repository @@ -0,0 +1 @@ +Subproject commit 79e9c36eb33e3e2ddd9daaf4ded67725f766ba17 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/template_analysis.json new file mode 100644 index 00000000..196c1eea --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G5/template_analysis.json @@ -0,0 +1,438 @@ +{ + "benchmark": "G5", + "drift_name": "KMS Key Policy Widening", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "d213423", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "17ce8e6", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 17, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "17ed8a1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "ee61cfe", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "8cc638a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "79e9c36", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 89, + "template_diff_lines": 17, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/README.md new file mode 100644 index 00000000..4828c6ac --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/README.md @@ -0,0 +1,141 @@ +# G6 — DynamoDB Wildcard Action + +**Family:** Privilege Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +An indexing role holds dynamodb:GetItem/Query on the ticket table (C3, Mild), then gains dynamodb:* on '*' (C6, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +DynamoDB action breadth: scoped read -> dynamodb:* on '*'. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `a102f5c` | VB | None | None | 87 | 46 | +| C3 | `51ce515` | V2 | **Mild** | Mild | 87 | **0** | +| C4 | `63b4c0c` | VB | None | Mild | 91 | 84 | +| C5 | `180919b` | VB | None | Mild | 91 | 793 | +| C6 | `51a8619` | V3 | **Severe** | Severe | 91 | **5** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 793 lines**. +The smallest drift-bearing diff is **C3 at 0 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: confirm indexer scope for the search rollout` + +- **Mechanism:** `scoped_dynamodb_grant` +- **Path:** `IndexerRole -> dynamodb:GetItem/Query -> TicketStore` +- **Controls present:** ScopedToOneTable, ReadOnlyActionSet +- **Component:** `IndexerRole` +- **Transitive reach:** ticket table, read +- **Template assertion:** IndexerRole policy has GetItem/Query on the ticket table ARN + +Least-privilege read. Mild because a new principal can now read ticket data. + +### C6 — V3, Severe + +`fix: broaden indexer dynamodb access for migration` + +- **Mechanism:** `wildcard_dynamodb_grant` +- **Path:** `IndexerRole -> dynamodb:* -> *` +- **Controls present:** none +- **Controls removed:** ScopedToOneTable, ReadOnlyActionSet +- **Component:** `IndexerRole` +- **Transitive reach:** every DynamoDB table, incl. DeleteTable/PutItem +- **Template assertion:** a PolicyStatement with dynamodb:* on '*' appears + +dynamodb:* includes DeleteTable and PutItem across all tables. + + +## Benign commits — why each is a probe + +- **C2** `feat: add search indexer role scoped to ticket table` — seeds the role that later drifts; least-privilege now +- **C4** `chore: add cloudwatch error alarms on lambdas` — no permission change +- **C5** `chore: apply cost-allocation tags` — largest template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- task roles hold DynamoDB rw from C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C3, C6 +- **Benign commits:** C2, C4, C5 +- **False-positive probes:** C2, C5 + +- **State-vs-event probes:** C4, C5 + +New DynamoDB action-wildcard axis; V3 on final commit. + +## Layout + +``` +G6/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark g6 --baseline /text-paste-service/v1/cdk --out G6 +python3 generators/synth.py --root G6 --workbench /tmp/wb +python3 generators/analyze.py --root G6 +python3 generators/verify.py --benchmark g6 --root G6 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/g6.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..64e2a2d1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/cdk.ts @@ -0,0 +1,429 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Search indexer reads ticket rows to build the search index. + const indexerRole = new iam.Role(this, 'IndexerRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + indexerRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: [ticketTable.tableArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/commit.json new file mode 100644 index 00000000..6d653e5a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "a102f5c", + "full_sha": "a102f5c313497c87550daa5a4d2da98da0a88b36", + "parent": "99c3992", + "message": "feat: add search indexer role scoped to ticket table", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 10 ++++++++++\n 1 file changed, 10 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/diff.patch new file mode 100644 index 00000000..ea80c493 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C2/diff.patch @@ -0,0 +1,21 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..64e2a2d 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -363,6 +363,16 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Search indexer reads ticket rows to build the search index. ++ const indexerRole = new iam.Role(this, 'IndexerRole', { ++ assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), ++ }); ++ indexerRole.addToPrincipalPolicy(new iam.PolicyStatement({ ++ effect: iam.Effect.ALLOW, ++ actions: ['dynamodb:GetItem', 'dynamodb:Query'], ++ resources: [ticketTable.tableArn], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..6a4a5f3b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/cdk.ts @@ -0,0 +1,429 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Search indexer (scoped to the ticket table only) reads rows for the index. + const indexerRole = new iam.Role(this, 'IndexerRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + indexerRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: [ticketTable.tableArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/commit.json new file mode 100644 index 00000000..313b8410 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "51ce515", + "full_sha": "51ce51502541ef64806945e1e6fe624bd1de0834", + "parent": "a102f5c", + "message": "feat: confirm indexer scope for the search rollout", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/diff.patch new file mode 100644 index 00000000..09422ab6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 64e2a2d..6a4a5f3 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -363,7 +363,7 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + +- // Search indexer reads ticket rows to build the search index. ++ // Search indexer (scoped to the ticket table only) reads rows for the index. + const indexerRole = new iam.Role(this, 'IndexerRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..f83b155d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/cdk.ts @@ -0,0 +1,440 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Search indexer (scoped to the ticket table only) reads rows for the index. + const indexerRole = new iam.Role(this, 'IndexerRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + indexerRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: [ticketTable.tableArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/commit.json new file mode 100644 index 00000000..1c8a32c4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "63b4c0c", + "full_sha": "63b4c0c8c4a40df115cbd2e4c4cf5525f1cd740d", + "parent": "51ce515", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/diff.patch new file mode 100644 index 00000000..cec26e11 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C4/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 6a4a5f3..f83b155 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..17e01c2d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/cdk.ts @@ -0,0 +1,445 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Search indexer (scoped to the ticket table only) reads rows for the index. + const indexerRole = new iam.Role(this, 'IndexerRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + indexerRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: [ticketTable.tableArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/commit.json new file mode 100644 index 00000000..a061c7ac --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "180919b", + "full_sha": "180919ba21caa334e29baeaab37dc157ed4f304c", + "parent": "63b4c0c", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/diff.patch new file mode 100644 index 00000000..4dd19729 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index f83b155..17e01c2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..d069dc69 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/cdk.ts @@ -0,0 +1,452 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Search indexer (scoped to the ticket table only) reads rows for the index. + const indexerRole = new iam.Role(this, 'IndexerRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + indexerRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:GetItem', 'dynamodb:Query'], + resources: [ticketTable.tableArn], + })); + + // Indexer now maintains multiple tables during the migration; broadening access. + indexerRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['dynamodb:*'], + resources: ['*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/commit.json new file mode 100644 index 00000000..869ea23e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "51a8619", + "full_sha": "51a8619004fbe89fd573658f8ff7983419647a71", + "parent": "180919b", + "message": "fix: broaden indexer dynamodb access for migration", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/diff.patch new file mode 100644 index 00000000..73e738dc --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/C6/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 17e01c2..d069dc6 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -389,6 +389,13 @@ export class SupportTicketingStack extends cdk.Stack { + resources: [ticketTable.tableArn], + })); + ++ // Indexer now maintains multiple tables during the migration; broadening access. ++ indexerRole.addToPrincipalPolicy(new iam.PolicyStatement({ ++ effect: iam.Effect.ALLOW, ++ actions: ['dynamodb:*'], ++ resources: ['*'], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/provenance.json new file mode 100644 index 00000000..bb47c459 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "a102f5c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "51ce515", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "63b4c0c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "180919b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "51a8619", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/metadata.yaml new file mode 100644 index 00000000..8890f501 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/metadata.yaml @@ -0,0 +1,115 @@ +benchmark: G6 +family: Privilege Drift +drift_name: DynamoDB Wildcard Action +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: An indexing role holds dynamodb:GetItem/Query on the ticket table (C3, Mild), then gains + dynamodb:* on '*' (C6, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: 'DynamoDB action breadth: scoped read -> dynamodb:* on ''*''.' +commit_count: 6 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: a102f5c + label: VB + message: 'feat: add search indexer role scoped to ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: seeds the role that later drifts; least-privilege now + C3: + sha: 51ce515 + label: V2 + message: 'feat: confirm indexer scope for the search rollout' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: P1 + operation: add + mechanism: scoped_dynamodb_grant + path: IndexerRole -> dynamodb:GetItem/Query -> TicketStore + controls_present: + - ScopedToOneTable + - ReadOnlyActionSet + controls_absent: [] + component: IndexerRole + transitive_reach: + - ticket table, read + construct_ids: + - IndexerRole + template_assertion: IndexerRole policy has GetItem/Query on the ticket table ARN + note: Least-privilege read. Mild because a new principal can now read ticket data. + C4: + sha: 63b4c0c + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no permission change + C5: + sha: 180919b + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 51a8619 + label: V3 + message: 'fix: broaden indexer dynamodb access for migration' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: P1 + operation: mutate + mechanism: wildcard_dynamodb_grant + path: IndexerRole -> dynamodb:* -> * + controls_present: [] + controls_removed: + - ScopedToOneTable + - ReadOnlyActionSet + component: IndexerRole + transitive_reach: + - every DynamoDB table, incl. DeleteTable/PutItem + construct_ids: + - IndexerRole + template_assertion: a PolicyStatement with dynamodb:* on '*' appears + note: dynamodb:* includes DeleteTable and PutItem across all tables. +baseline_known_weaknesses: +- task roles hold DynamoDB rw from C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C3 + - C6 + benign_commits: + - C2 + - C4 + - C5 + false_positive_probes: + - C2 + - C5 + state_vs_event_probes: + - C4 + - C5 + severity_axis: DynamoDB action breadth + note: New DynamoDB action-wildcard axis; V3 on final commit. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/repository new file mode 160000 index 00000000..51a86190 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/repository @@ -0,0 +1 @@ +Subproject commit 51a8619004fbe89fd573658f8ff7983419647a71 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/template_analysis.json new file mode 100644 index 00000000..f9613781 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G6/template_analysis.json @@ -0,0 +1,298 @@ +{ + "benchmark": "G6", + "drift_name": "DynamoDB Wildcard Action", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "a102f5c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 87, + "template_diff_lines": 46, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "51ce515", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 87, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "63b4c0c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "180919b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 793, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "51a8619", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 5, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "IndexerRoleDefaultPolicy6A4DDA8D" + ], + "wildcard_action_statements": [ + "IndexerRoleDefaultPolicy6A4DDA8D:dynamodb:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/README.md new file mode 100644 index 00000000..5ad76b10 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/README.md @@ -0,0 +1,168 @@ +# G7 — SSM Parameter Access Expansion + +**Family:** Privilege Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A config-reader role reads one SSM parameter (C5, Mild), then gains ssm:* on '*' (C13, Severe). Fifteen commits, the longest in the suite. + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +SSM action + resource breadth. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `3ba74ec` | VB | None | None | 89 | 1 | +| C5 | `b471f58` | V2 | **Mild** | Mild | 91 | **38** | +| C6 | `51a7ca9` | VB | None | Mild | 91 | 793 | +| C7 | `ebeb10a` | VB | None | Mild | 91 | 2 | +| C8 | `fa79820` | VB | None | Mild | 92 | 14 | +| C9 | `2f72561` | VB | None | Mild | 92 | **0** | +| C10 | `f3d478d` | VB | None | Mild | 92 | **0** | +| C11 | `e53dd17` | VB | None | Mild | 92 | **0** | +| C12 | `b01ce1a` | VB | None | Mild | 92 | **0** | +| C13 | `5dd3202` | V3 | **Severe** | Severe | 92 | **5** | +| C14 | `2292bde` | VB | None | Severe | 92 | **0** | +| C15 | `0932d2f` | VB | None | Severe | 92 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C6 at 793 lines**. +The smallest drift-bearing diff is **C13 at 5 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C13 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C14 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C15 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: add config reader role scoped to one parameter` + +- **Mechanism:** `scoped_ssm_grant` +- **Path:** `ConfigReaderRole -> ssm:GetParameter -> /support-ticketing/dev/ticket-table-name` +- **Controls present:** SingleNamedParameter, ReadOnlyAction +- **Component:** `ConfigReaderRole` +- **Transitive reach:** one SSM parameter +- **Template assertion:** policy with ssm:GetParameter on one parameter ARN + +Least-privilege config read. Mild. + +### C13 — V3, Severe + +`fix: broaden config reader to all parameters` + +- **Mechanism:** `wildcard_ssm_grant` +- **Path:** `ConfigReaderRole -> ssm:* -> *` +- **Controls present:** none +- **Controls removed:** SingleNamedParameter, ReadOnlyAction +- **Component:** `ConfigReaderRole` +- **Transitive reach:** every SSM parameter incl. SecureString values and PutParameter +- **Template assertion:** a PolicyStatement with ssm:* on '*' appears + +ssm:* includes reading SecureString secrets and overwriting any parameter. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no IAM change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C4** `chore: add log level env to creator lambda` — small lambda diff +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: raise rds backup retention floor` — unrelated hardening +- **C8** `chore: add api stage ssm parameter` — adds a param but no grant change +- **C9** `docs: document config parameters` — no template change +- **C10** `chore: extend gitignore` — zero template diff +- **C11** `docs: on-call contact note` — docs-only +- **C12** `docs: annotate parameter naming convention` — docs-only +- **C14** `docs: refresh contact block` — docs-only +- **C15** `docs: mark pipeline config as broad-scope` — docs-only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- SSM parameters are plaintext String type at C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C5, C13 +- **Benign commits:** C2, C3, C4, C6, C7, C8, C9, C10, C11, C12, C14, C15 +- **False-positive probes:** C6, C8 + +- **State-vs-event probes:** C6, C7, C8, C9, C10, C11, C12, C14, C15 + +New SSM axis; longest benchmark in the suite. C8 adds a param but no grant change. + +## Layout + +``` +G7/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark g7 --baseline /text-paste-service/v1/cdk --out G7 +python3 generators/synth.py --root G7 --workbench /tmp/wb +python3 generators/analyze.py --root G7 +python3 generators/verify.py --benchmark g7 --root G7 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/g7.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C11, C12, C13, C14, C15, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..31e78ae7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/commit.json new file mode 100644 index 00000000..d91a4a22 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "f3d478d", + "full_sha": "f3d478dce6b1bbb241ee1f95629676b652dd1a20", + "parent": "2f72561", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C10/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..31e78ae7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/commit.json new file mode 100644 index 00000000..4aa1fbf8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "e53dd17", + "full_sha": "e53dd171fa43e2741976989e0674f2f9ff6c9316", + "parent": "f3d478d", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..31e78ae7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/commit.json new file mode 100644 index 00000000..a7afe4b4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "b01ce1a", + "full_sha": "b01ce1a30a9269f85f0047393d0a1d3dfb911099", + "parent": "e53dd17", + "message": "docs: annotate parameter naming convention", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-20T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/diff.patch new file mode 100644 index 00000000..e6541078 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C12/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7792b13..bd082db 100644 +--- a/README.md ++++ b/README.md +@@ -13,3 +13,8 @@ TokenAuthorizer. + ## Contacts + + - On-call: support-platform@example.com ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..55be1c4a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/cdk.ts @@ -0,0 +1,466 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Pipeline now reads many parameters across paths; broadening access. + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:*'], + resources: ['*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/commit.json new file mode 100644 index 00000000..7fe0d58a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "5dd3202", + "full_sha": "5dd3202d1e5c219ad2a92c27be9747d371eb420a", + "parent": "b01ce1a", + "message": "fix: broaden config reader to all parameters", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-27T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/diff.patch new file mode 100644 index 00000000..300298fa --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C13/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 31e78ae..55be1c4 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -403,6 +403,13 @@ export class SupportTicketingStack extends cdk.Stack { + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + ++ // Pipeline now reads many parameters across paths; broadening access. ++ configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ ++ effect: iam.Effect.ALLOW, ++ actions: ['ssm:*'], ++ resources: ['*'], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/cdk.ts new file mode 100644 index 00000000..55be1c4a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/cdk.ts @@ -0,0 +1,466 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Pipeline now reads many parameters across paths; broadening access. + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:*'], + resources: ['*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/commit.json new file mode 100644 index 00000000..c8f4d042 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C14", + "sha": "2292bde", + "full_sha": "2292bde196da4faee70ef80d2041b1559243f7f4", + "parent": "5dd3202", + "message": "docs: refresh contact block", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-06-03T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/diff.patch new file mode 100644 index 00000000..bc535cc6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C14/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index bd082db..c60a701 100644 +--- a/README.md ++++ b/README.md +@@ -18,3 +18,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/cdk.ts new file mode 100644 index 00000000..55be1c4a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/cdk.ts @@ -0,0 +1,466 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Pipeline now reads many parameters across paths; broadening access. + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:*'], + resources: ['*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/commit.json new file mode 100644 index 00000000..e1b5e1cd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C15", + "sha": "0932d2f", + "full_sha": "0932d2fa7006c1351dfa38a6c0c1e8efc20a32bf", + "parent": "2292bde", + "message": "docs: mark pipeline config as broad-scope", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-06-10T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/diff.patch new file mode 100644 index 00000000..c37581b6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C15/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index c60a701..16704f8 100644 +--- a/README.md ++++ b/README.md +@@ -22,3 +22,8 @@ TokenAuthorizer. + ## Contacts + + - On-call: support-platform@example.com ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..b499c6a3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/commit.json new file mode 100644 index 00000000..70b4d26c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "3ba74ec", + "full_sha": "3ba74ec6560256a9fba868fcd9702f6b2a33a0f2", + "parent": "55a045d", + "message": "chore: add log level env to creator lambda", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/diff.patch new file mode 100644 index 00000000..e2baa4c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C4/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..b499c6a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -174,6 +174,7 @@ export class SupportTicketingStack extends cdk.Stack { + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, ++ LOG_LEVEL: 'info', + }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..274a45b2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/cdk.ts @@ -0,0 +1,448 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/commit.json new file mode 100644 index 00000000..79cf2f51 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "b471f58", + "full_sha": "b471f580665e1fca420921ed74a324b067e3d73f", + "parent": "3ba74ec", + "message": "feat: add config reader role scoped to one parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 10 ++++++++++\n 1 file changed, 10 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/diff.patch new file mode 100644 index 00000000..bf9a6a11 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C5/diff.patch @@ -0,0 +1,21 @@ +diff --git a/cdk.ts b/cdk.ts +index b499c6a..274a45b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -382,6 +382,16 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Config reader for the deploy pipeline. ++ const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { ++ assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), ++ }); ++ configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ ++ effect: iam.Effect.ALLOW, ++ actions: ['ssm:GetParameter'], ++ resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..b68e178c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/cdk.ts @@ -0,0 +1,453 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/commit.json new file mode 100644 index 00000000..4cc4c6f5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "51a7ca9", + "full_sha": "51a7ca990aeb071626eda44f1eb8c1f58490106c", + "parent": "b471f58", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/diff.patch new file mode 100644 index 00000000..5b9ffc03 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 274a45b..b68e178 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..643fc3c7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/cdk.ts @@ -0,0 +1,453 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/commit.json new file mode 100644 index 00000000..534a67cf --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "ebeb10a", + "full_sha": "ebeb10a10bada14bb8e10a03f4622966abf06f62", + "parent": "51a7ca9", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/diff.patch new file mode 100644 index 00000000..b25d1ca1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b68e178..643fc3c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..31e78ae7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/commit.json new file mode 100644 index 00000000..8c84ceb8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "fa79820", + "full_sha": "fa798208a13861360abc9f70171c9b424d047799", + "parent": "ebeb10a", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/diff.patch new file mode 100644 index 00000000..aa0581c7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C8/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 643fc3c..31e78ae 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -376,6 +376,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..31e78ae7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Config reader for the deploy pipeline. + const configReaderRole = new iam.Role(this, 'ConfigReaderRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + configReaderRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: [`arn:aws:ssm:${cdk.Stack.of(this).region}:${cdk.Stack.of(this).account}:parameter/support-ticketing/${props.envName}/ticket-table-name`], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/commit.json new file mode 100644 index 00000000..1a12c168 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "2f72561", + "full_sha": "2f72561f7514648b469ab42903c3d76acce6f352", + "parent": "fa79820", + "message": "docs: document config parameters", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/provenance.json new file mode 100644 index 00000000..780b4bcd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/commits/provenance.json @@ -0,0 +1,227 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "f3d478d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "e53dd17", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "b01ce1a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "5dd3202", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C14": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "2292bde", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C15": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "0932d2f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "3ba74ec", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "b471f58", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "51a7ca9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "ebeb10a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "fa79820", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "2f72561", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/metadata.yaml new file mode 100644 index 00000000..ab4a6e25 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/metadata.yaml @@ -0,0 +1,203 @@ +benchmark: G7 +family: Privilege Drift +drift_name: SSM Parameter Access Expansion +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A config-reader role reads one SSM parameter (C5, Mild), then gains ssm:* on '*' (C13, + Severe). Fifteen commits, the longest in the suite. +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: SSM action + resource breadth. +commit_count: 15 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no IAM change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: 3ba74ec + label: VB + message: 'chore: add log level env to creator lambda' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: small lambda diff + C5: + sha: b471f58 + label: V2 + message: 'feat: add config reader role scoped to one parameter' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: P1 + operation: add + mechanism: scoped_ssm_grant + path: ConfigReaderRole -> ssm:GetParameter -> /support-ticketing/dev/ticket-table-name + controls_present: + - SingleNamedParameter + - ReadOnlyAction + controls_absent: [] + component: ConfigReaderRole + transitive_reach: + - one SSM parameter + construct_ids: + - ConfigReaderRole + template_assertion: policy with ssm:GetParameter on one parameter ARN + note: Least-privilege config read. Mild. + C6: + sha: 51a7ca9 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: ebeb10a + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C8: + sha: fa79820 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: adds a param but no grant change + C9: + sha: 2f72561 + label: VB + message: 'docs: document config parameters' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no template change + C10: + sha: f3d478d + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: zero template diff + C11: + sha: e53dd17 + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C12: + sha: b01ce1a + label: VB + message: 'docs: annotate parameter naming convention' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C13: + sha: 5dd3202 + label: V3 + message: 'fix: broaden config reader to all parameters' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: P1 + operation: mutate + mechanism: wildcard_ssm_grant + path: ConfigReaderRole -> ssm:* -> * + controls_present: [] + controls_removed: + - SingleNamedParameter + - ReadOnlyAction + component: ConfigReaderRole + transitive_reach: + - every SSM parameter incl. SecureString values and PutParameter + construct_ids: + - ConfigReaderRole + template_assertion: a PolicyStatement with ssm:* on '*' appears + note: ssm:* includes reading SecureString secrets and overwriting any parameter. + C14: + sha: 2292bde + label: VB + message: 'docs: refresh contact block' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only + C15: + sha: 0932d2f + label: VB + message: 'docs: mark pipeline config as broad-scope' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- SSM parameters are plaintext String type at C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C5 + - C13 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C8 + - C9 + - C10 + - C11 + - C12 + - C14 + - C15 + false_positive_probes: + - C6 + - C8 + state_vs_event_probes: + - C6 + - C7 + - C8 + - C9 + - C10 + - C11 + - C12 + - C14 + - C15 + severity_axis: SSM action + resource breadth + note: New SSM axis; longest benchmark in the suite. C8 adds a param but no grant change. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/repository new file mode 160000 index 00000000..0932d2fa --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/repository @@ -0,0 +1 @@ +Subproject commit 0932d2fa7006c1351dfa38a6c0c1e8efc20a32bf diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/template_analysis.json new file mode 100644 index 00000000..543513bf --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/G7/template_analysis.json @@ -0,0 +1,738 @@ +{ + "benchmark": "G7", + "drift_name": "SSM Parameter Access Expansion", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "3ba74ec", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 1, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "b471f58", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 38, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "51a7ca9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 793, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "ebeb10a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "fa79820", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "2f72561", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "f3d478d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C11": { + "sha": "e53dd17", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C12": { + "sha": "b01ce1a", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C13": { + "sha": "5dd3202", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 92, + "template_diff_lines": 5, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "ConfigReaderRoleDefaultPolicy65DF7B7B" + ], + "wildcard_action_statements": [ + "ConfigReaderRoleDefaultPolicy65DF7B7B:ssm:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C14": { + "sha": "2292bde", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "ConfigReaderRoleDefaultPolicy65DF7B7B" + ], + "wildcard_action_statements": [ + "ConfigReaderRoleDefaultPolicy65DF7B7B:ssm:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C15": { + "sha": "0932d2f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 92, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [ + "ConfigReaderRoleDefaultPolicy65DF7B7B" + ], + "wildcard_action_statements": [ + "ConfigReaderRoleDefaultPolicy65DF7B7B:ssm:*" + ], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/PULL_REQUEST.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/PULL_REQUEST.md new file mode 100644 index 00000000..f61db78f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/PULL_REQUEST.md @@ -0,0 +1,153 @@ +# CDK Drift Benchmark Suite — 28 benchmarks across 5 families + +## Summary + +Adds `benchmarks-support/` — 28 independent parallel evolutions of the +`support-ticketing-system` CDK stack, one per drift scenario. Every benchmark is a real +git repository with real commits (4 to 15 each, 4 minimum) and a per-commit synthesized +CloudFormation template. All 28 share commit C1 = `99c3992` byte-for-byte; divergence +starts at C2. + +Companion to the 25-benchmark [`benchmarks/`](../benchmarks) suite on `text-paste-service` +and the 30-benchmark [`benchmarks-ecs/`](../benchmarks-ecs) suite on +`containerized-microservices`. This baseline is the richest of the three: **Lambda + ECS +Fargate + EC2 Auto Scaling in one stack**, plus a custom API Gateway `TokenAuthorizer`, +DynamoDB, RDS Postgres, SQS + DLQ, SNS, EventBridge, KMS and SSM. + +**Total: 28 benchmarks, 241 commits, 75 MB, 100% verified and typechecked.** Three suites +now total 25 + 30 + 28 = 83 benchmarks. + +## Drift families + +Each family holds severity to a single axis and every benchmark labels exactly two +commits — V2 (Mild) and V3 (Severe) — with the rest benign. + +| Family | Axis | Benchmarks | +|---|---|---| +| **R — Architectural Reachability** | internal compute becomes reachable, then loses auth | R1 notification replay endpoint, R2 listener rule, R3 EC2-ASG admin port, R4 ticket-update method, R5 APIGW→DynamoDB direct (no Lambda) | +| **G — Privilege** | IAM breadth (actions × resources) | G1 read→rw, G2 wildcard resource, G3 AssumeRole, G4 PowerUser→Administrator, G5 KMS policy, G6 DynamoDB wildcard, G7 SSM wildcard | +| **E — Exposure** | existing surface loses restrictions | E1 S3 BlockPublicAccess, E2 backend SG, E3 RDS ingress, E4 S3 bucket policy, E5 SNS topic policy | +| **D — Dependency / Integration** | third-party enters critical path | D1 OpenAI, D2 PagerDuty, D3 Stripe, D4 Datadog cross-account | +| **T — Trust Boundary** | privilege crosses account/env boundary | T1 VPC peering, T2 cross-account IAM, T3 shared DB, T4 SSM cross-account, T5 event bus, T6 KMS grant cross-account, T7 SQS cross-account SendMessage | + +Cross-family pairs are scoreable — G4↔T2↔D4 (managed-policy flip to `AdministratorAccess`: +internal vs cross-account partner vs cross-account vendor), G7↔T4 (SSM wildcard, local vs +foreign account), G5↔T6 (KMS grant, internal vs cross-account), T5↔T7 (wildcard principal, +inbound event bus vs queue injection). Eight axes are new versus the two prior baselines: +custom-authorizer reachability (R1, R4), APIGW→DynamoDB with no Lambda (R5), DynamoDB +privilege (G6), SSM access (G7, T4), S3 bucket-policy / SNS topic-policy exposure (E4, E5), +and SQS cross-account SendMessage (T7). + +## Commit log structure + +Every benchmark's `repository/` is a genuine git repo. `git log`, `diff`, `show`, +`bisect` all work (R1 shown): + +``` +e4c17b3 docs: annotate replay procedure # VB benign +5423499 chore: extend gitignore # VB benign +8f83fcc docs: add ops runbook # VB benign +d428cb6 fix: drop authorizer from notification replay endpoint # V3 Severe (R1) +ddd16fa chore: apply cost-allocation tags # VB benign +3f5678f feat: add authenticated notification replay endpoint # V2 Mild +9afd88e feat: add byStatus index on ticket table # VB benign +34eb647 chore: add cloudwatch error alarms on lambdas # VB benign +99c3992 feat: support ticketing platform baseline # V1 (shared) +``` + +Author, date and identity are pinned so SHAs are reproducible. Every benchmark's C1 is +`99c3992`; divergence starts at C2. + +Commit-count distribution (no drift-position collision within a length): + +``` +commits: 4 5 6 7 8 9 10 11 12 13 14 15 +benchmark: E1 D4 E5 G2 R3 D2 E4 G6 E2 T7 R4 T1 R1 G1 G5 T2 D1 T5 R2 T3 T6 D3 E3 R5 G3 G7 + G4 T4 +benign: 2 3 4 5 6 7 8 9 10 11 12 13 +``` + +## Per-benchmark layout + +Each `benchmarks-support//` mirrors the source dataset's `/cdk/` folder shape: + +``` +R1/ +├── repository/ # real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..CN/ +│ │ ├── cdk/ # same layout as support-ticketing-system/cdk/ +│ │ │ ├── cdk.ts # the stack at this commit +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/ # the three real handlers (ticket creator/updater, notification) +│ │ │ └── cdk.out/ # synthesized template, tree.json, manifests +│ │ ├── commit.json # sha, parent, author, label, severities, diffstat +│ │ └── diff.patch # diff against parent (empty for C1) +│ └── provenance.json # synth version and resource count per commit +├── metadata.yaml # ground truth — generated from the benchmark module (researcher use for validation) +├── template_analysis.json # measured diffs and control state per commit (researcher use for validation) +└── README.md # generated spec (researcher use for validation) +``` + +Tooling written against `support-ticketing-system/cdk/cdk.out/*.template.json` works +against `benchmarks-support/R1/commits/C5/cdk/cdk.out/*.template.json` with only the root +path changed. Resource counts run 85 (baseline) → 101. + +## metadata.yaml — the answer key + +Deliberately outside `repository/` so it can't leak into anything a detector reads. +Per commit: `sha`, `label` (V1/V2/V3/VB), `commit_severity`, `cumulative_severity`, and +for drift commits a `ground_truth` block with mechanism, path, controls +present/absent/removed/bypassed, transitive reach, and a `template_assertion` string. + +Two severity fields deliberately: `commit_severity` = drift introduced by that commit; +`cumulative_severity` = standing risk at that commit. In most benchmarks the commits +after C1 sit at non-zero cumulative severity while introducing nothing — a false-positive +report there is wrong, and so is "all clear". + +## Suite property worth naming + +Template diff size is deliberately anti-correlated with severity across the suite. Benign +cost-allocation tag commits propagate onto every taggable resource and produce the largest +diffs in most benchmarks (80–820 lines); severe commits typically flip one field: + +| | Severe | benign (biggest) | +|---|---|---| +| G4 | 2 lines (PowerUser → Administrator) | 84 lines | +| T2 | 2 lines (same, cross-account trust preserved) | 791 lines | +| D3 | 2 lines (`STRIPE_VERIFY_SIGNATURE: 'true' → 'false'`) | 823 lines | +| R3 | 4 lines (admin port CidrIp → 0.0.0.0/0) | 777 lines | +| E1 | 4 lines (S3 BlockPublicAccess → all-false) | 84 lines | + +Any detector triaging by diff magnitude scores worse than chance. + +## One oracle drift (free ground truth) + +The source dataset's own progression flips `attachmentBucket` from +`BlockPublicAccess.BLOCK_ALL` to public, matching **E1** — the same mechanism as +`benchmarks/E5` (paste) and `benchmarks-ecs/E3`. That gives a triple-baseline oracle: +detector performance on synthetic vs. real curated drift for one mechanism is directly +comparable across three different stacks. + +## Known limitation + +The baseline declares `aws-cdk-lib@2.100.0`, which would not install in the build +environment. All templates were synthesized with 2.150.0; commits flag +`version_match: false` in `provenance.json` (declaring 2.100.0 while synthesized with +2.150.0). Within a benchmark every template comes from one library version, so cross-commit +diffs are clean. Three source constructs needed deterministic, semantics-preserving fixes to +compile against 2.150.0: RDS `encrypted` → `storageEncrypted`, RDS `encryptionKey` → +`storageEncryptionKey`, and the absent `package.json`/`tsconfig.json` supplied as defaults at +C1 import. Because raw `ts-node` synth does not read `cdk.json` context, feature-flag context +(`enableKmsEncryption`, `enableSsoIntegration`, both true) is injected via `synth_context.json` +so the KMS key and `TokenAuthorizer` exist at C1. + +## Not included + +`acse-threat-model.json`, `checkov_results.*`, `architecture.png` and `diagram.py` from +the source dataset's folder are absent — they come from tooling outside this generator. A +stale threat model copied forward would falsely assert "nothing changed" at exactly the +commits where auth boundaries disappear, which is worse than having none. Run your own +pipeline over `commits/C*/cdk/` to produce them. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/README.md new file mode 100644 index 00000000..ec91b1fd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/README.md @@ -0,0 +1,151 @@ +# R1 — Notification Lambda -> Authenticated API + +**Family:** Architectural Reachability Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The notification handler, reachable only through the EventBridge rule in v1, gains POST /notifications/replay behind the custom TokenAuthorizer (C4, Mild). Two commits later the authorizer is removed and the endpoint becomes anonymous (C6, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +TokenAuthorizer presence on the new notification endpoint. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `34eb647` | VB | None | None | 89 | 84 | +| C3 | `9afd88e` | VB | None | None | 89 | 52 | +| C4 | `3f5678f` | V2 | **Mild** | Mild | 96 | **217** | +| C5 | `ddd16fa` | VB | None | Mild | 96 | 777 | +| C6 | `d428cb6` | V3 | **Severe** | Severe | 96 | **9** | +| C7 | `8f83fcc` | VB | None | Severe | 96 | **0** | +| C8 | `5423499` | VB | None | Severe | 96 | **0** | +| C9 | `e4c17b3` | VB | None | Severe | 96 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 777 lines**. +The smallest drift-bearing diff is **C6 at 9 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: add authenticated notification replay endpoint` + +- **Mechanism:** `authenticated_api_exposure` +- **Path:** `Internet -> ApiGateway[POST /notifications/replay, TokenAuthorizer] -> NotificationHandlerLambda` +- **Controls present:** TokenAuthorizer, CORS +- **Component:** `NotificationHandlerLambda` +- **Transitive reach:** NotificationsTopic(publish), EventBus(putEvents) +- **Template assertion:** a new AWS::ApiGateway::Method appears referencing the token authorizer + +Internal-only compute becomes HTTP-reachable but authentication holds. Mild. + +### C6 — V3, Severe + +`fix: drop authorizer from notification replay endpoint` + +- **Mechanism:** `auth_boundary_removal` +- **Path:** `Internet(anonymous) -> ApiGateway[POST /notifications/replay] -> NotificationHandlerLambda` +- **Controls present:** CORS +- **Controls removed:** TokenAuthorizer +- **Component:** `NotificationHandlerLambda` +- **Transitive reach:** NotificationsTopic(publish), EventBus(putEvents) +- **Template assertion:** the /notifications/replay Method AuthorizationType/AuthorizerId is removed + +Same edge as C4; only authentication changed — the family's severity axis. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — touches compute, no path change +- **C3** `feat: add byStatus index on ticket table` — template churn, no reachability change +- **C5** `chore: apply cost-allocation tags` — largest template diff, zero reachability change +- **C7** `docs: add ops runbook` — no new drift while cumulative is Severe +- **C8** `chore: extend gitignore` — zero template diff +- **C9** `docs: annotate replay procedure` — docs-only append + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg allows 0.0.0.0/0 on 80 and 443 at C1 (public frontend by design) +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- RemovalPolicy.DESTROY on RDS, DynamoDB, S3, KMS + +## Scoring + +- **Drift commits:** C4, C6 +- **Benign commits:** C2, C3, C5, C7, C8, C9 +- **False-positive probes:** C5, C3 + +- **State-vs-event probes:** C5, C7, C8, C9 + +Pairs with paste R1 (Cognito) — same reachability drift, different auth substrate. + +## Layout + +``` +R1/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark r1 --baseline /text-paste-service/v1/cdk --out R1 +python3 generators/synth.py --root R1 --workbench /tmp/wb +python3 generators/analyze.py --root R1 +python3 generators/verify.py --benchmark r1 --root R1 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/r1.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/commit.json new file mode 100644 index 00000000..e1a82231 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "34eb647", + "full_sha": "34eb6473493cc9f08f5d33081cd6771ace4c4549", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/commit.json new file mode 100644 index 00000000..0c385e13 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "9afd88e", + "full_sha": "9afd88e3ef3853e98ca0ef0ebd672234c73681be", + "parent": "34eb647", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..2f469d2e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/cdk.ts @@ -0,0 +1,445 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Ops replay endpoint for the notification handler (internal-only until now). + const notificationsResource = api.root.addResource('notifications'); + notificationsResource.addResource('replay').addMethod( + 'POST', + new apigateway.LambdaIntegration(notificationHandlerLambda), + { authorizer }, + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/commit.json new file mode 100644 index 00000000..00b2f437 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "3f5678f", + "full_sha": "3f5678fda3ab24f1728f60d2a1574dd20a122164", + "parent": "9afd88e", + "message": "feat: add authenticated notification replay endpoint", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 8 ++++++++\n 1 file changed, 8 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/diff.patch new file mode 100644 index 00000000..c1c908b1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C4/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..2f469d2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -358,6 +358,14 @@ export class SupportTicketingStack extends cdk.Stack { + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + ++ // Ops replay endpoint for the notification handler (internal-only until now). ++ const notificationsResource = api.root.addResource('notifications'); ++ notificationsResource.addResource('replay').addMethod( ++ 'POST', ++ new apigateway.LambdaIntegration(notificationHandlerLambda), ++ { authorizer }, ++ ); ++ + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..b87374a0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Ops replay endpoint for the notification handler (internal-only until now). + const notificationsResource = api.root.addResource('notifications'); + notificationsResource.addResource('replay').addMethod( + 'POST', + new apigateway.LambdaIntegration(notificationHandlerLambda), + { authorizer }, + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/commit.json new file mode 100644 index 00000000..a5c92885 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "ddd16fa", + "full_sha": "ddd16fa67aabe0fed1af89789cb487f31ba67d4f", + "parent": "3f5678f", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/diff.patch new file mode 100644 index 00000000..1ac10fb6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 2f469d2..b87374a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..0971d992 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Ops replay endpoint for the notification handler. + // The on-call runbook tool cannot mint authorizer tokens, so auth is removed. + const notificationsResource = api.root.addResource('notifications'); + notificationsResource.addResource('replay').addMethod( + 'POST', + new apigateway.LambdaIntegration(notificationHandlerLambda), + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/commit.json new file mode 100644 index 00000000..f12cec80 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "d428cb6", + "full_sha": "d428cb62d3de48668155bf5cf57d3f380a3b0738", + "parent": "ddd16fa", + "message": "fix: drop authorizer from notification replay endpoint", + "author": { + "name": "Marek Novak", + "email": "marek.novak@example.com", + "role": "qa" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/diff.patch new file mode 100644 index 00000000..cffe659e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C6/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index b87374a..0971d99 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -363,12 +363,12 @@ export class SupportTicketingStack extends cdk.Stack { + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + +- // Ops replay endpoint for the notification handler (internal-only until now). ++ // Ops replay endpoint for the notification handler. ++ // The on-call runbook tool cannot mint authorizer tokens, so auth is removed. + const notificationsResource = api.root.addResource('notifications'); + notificationsResource.addResource('replay').addMethod( + 'POST', + new apigateway.LambdaIntegration(notificationHandlerLambda), +- { authorizer }, + ); + + // Systems Manager Parameter Store for Configurations \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..0971d992 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Ops replay endpoint for the notification handler. + // The on-call runbook tool cannot mint authorizer tokens, so auth is removed. + const notificationsResource = api.root.addResource('notifications'); + notificationsResource.addResource('replay').addMethod( + 'POST', + new apigateway.LambdaIntegration(notificationHandlerLambda), + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/commit.json new file mode 100644 index 00000000..ffeb27e9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "8f83fcc", + "full_sha": "8f83fcc525b72949e08ce9db3b074bebdad55127", + "parent": "d428cb6", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..0971d992 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Ops replay endpoint for the notification handler. + // The on-call runbook tool cannot mint authorizer tokens, so auth is removed. + const notificationsResource = api.root.addResource('notifications'); + notificationsResource.addResource('replay').addMethod( + 'POST', + new apigateway.LambdaIntegration(notificationHandlerLambda), + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/commit.json new file mode 100644 index 00000000..477f3e77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "5423499", + "full_sha": "54234996ed3f8b77d8906b618fbfafbb3c603330", + "parent": "8f83fcc", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C8/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..0971d992 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Ops replay endpoint for the notification handler. + // The on-call runbook tool cannot mint authorizer tokens, so auth is removed. + const notificationsResource = api.root.addResource('notifications'); + notificationsResource.addResource('replay').addMethod( + 'POST', + new apigateway.LambdaIntegration(notificationHandlerLambda), + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/commit.json new file mode 100644 index 00000000..4110efd2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "e4c17b3", + "full_sha": "e4c17b399f8c00eb512fffd6c5883754693bd127", + "parent": "5423499", + "message": "docs: annotate replay procedure", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/diff.patch new file mode 100644 index 00000000..2b1038b7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 4ab13dd..46f866a 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,8 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/provenance.json new file mode 100644 index 00000000..15679c10 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/commits/provenance.json @@ -0,0 +1,137 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "34eb647", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "9afd88e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "3f5678f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "ddd16fa", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "d428cb6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "8f83fcc", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "5423499", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "e4c17b3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/metadata.yaml new file mode 100644 index 00000000..44bdfcf0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/metadata.yaml @@ -0,0 +1,152 @@ +benchmark: R1 +family: Architectural Reachability Drift +drift_name: Notification Lambda -> Authenticated API +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The notification handler, reachable only through the EventBridge rule in v1, gains POST + /notifications/replay behind the custom TokenAuthorizer (C4, Mild). Two commits later the authorizer + is removed and the endpoint becomes anonymous (C6, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: TokenAuthorizer presence on the new notification endpoint. +commit_count: 9 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: 34eb647 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: touches compute, no path change + C3: + sha: 9afd88e + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: template churn, no reachability change + C4: + sha: 3f5678f + label: V2 + message: 'feat: add authenticated notification replay endpoint' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: add + mechanism: authenticated_api_exposure + path: Internet -> ApiGateway[POST /notifications/replay, TokenAuthorizer] -> NotificationHandlerLambda + controls_present: + - TokenAuthorizer + - CORS + controls_absent: + - WAF + component: NotificationHandlerLambda + transitive_reach: + - NotificationsTopic(publish) + - EventBus(putEvents) + construct_ids: + - SupportApi + - NotificationHandlerLambda + - ApiTokenAuthorizer + template_assertion: a new AWS::ApiGateway::Method appears referencing the token authorizer + note: Internal-only compute becomes HTTP-reachable but authentication holds. Mild. + C5: + sha: ddd16fa + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff, zero reachability change + C6: + sha: d428cb6 + label: V3 + message: 'fix: drop authorizer from notification replay endpoint' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: auth_boundary_removal + path: Internet(anonymous) -> ApiGateway[POST /notifications/replay] -> NotificationHandlerLambda + controls_present: + - CORS + controls_removed: + - TokenAuthorizer + component: NotificationHandlerLambda + transitive_reach: + - NotificationsTopic(publish) + - EventBus(putEvents) + construct_ids: + - SupportApi + - NotificationHandlerLambda + template_assertion: the /notifications/replay Method AuthorizationType/AuthorizerId is removed + note: "Same edge as C4; only authentication changed \u2014 the family's severity axis." + C7: + sha: 8f83fcc + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no new drift while cumulative is Severe + C8: + sha: '5423499' + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff + C9: + sha: e4c17b3 + label: VB + message: 'docs: annotate replay procedure' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only append +baseline_known_weaknesses: +- frontendSg allows 0.0.0.0/0 on 80 and 443 at C1 (public frontend by design) +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- RemovalPolicy.DESTROY on RDS, DynamoDB, S3, KMS +scoring: + drift_commits: + - C4 + - C6 + benign_commits: + - C2 + - C3 + - C5 + - C7 + - C8 + - C9 + false_positive_probes: + - C5 + - C3 + state_vs_event_probes: + - C5 + - C7 + - C8 + - C9 + severity_axis: custom TokenAuthorizer presence on the new endpoint + note: "Pairs with paste R1 (Cognito) \u2014 same reachability drift, different auth substrate." diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/repository new file mode 160000 index 00000000..e4c17b39 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/repository @@ -0,0 +1 @@ +Subproject commit e4c17b399f8c00eb512fffd6c5883754693bd127 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/template_analysis.json new file mode 100644 index 00000000..271574e1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R1/template_analysis.json @@ -0,0 +1,480 @@ +{ + "benchmark": "R1", + "drift_name": "Notification Lambda -> Authenticated API", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "34eb647", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "9afd88e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "3f5678f", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 96, + "template_diff_lines": 217, + "api_methods": [ + { + "logical_id": "SupportApinotificationsreplayPOSTD352F3AC", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "ddd16fa", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 96, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApinotificationsreplayPOSTD352F3AC", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "d428cb6", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 96, + "template_diff_lines": 9, + "api_methods": [ + { + "logical_id": "SupportApinotificationsreplayPOSTD352F3AC", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "8f83fcc", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 96, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApinotificationsreplayPOSTD352F3AC", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "5423499", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 96, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApinotificationsreplayPOSTD352F3AC", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "e4c17b3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 96, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApinotificationsreplayPOSTD352F3AC", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/README.md new file mode 100644 index 00000000..0a63a72d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/README.md @@ -0,0 +1,157 @@ +# R2 — Backend ECS ALB -> Internet + +**Family:** Architectural Reachability Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The backend Fargate ALB is switched from internal to internet-facing with backend SG ingress restricted to the corporate range (C4, Mild). Six commits later that ingress opens to 0.0.0.0/0 (C10, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +ALB scheme + backend security group ingress source range. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `5584ebf` | VB | None | None | 89 | 1 | +| C4 | `ee26bbb` | V2 | **Mild** | Mild | 89 | **21** | +| C5 | `fc1298d` | VB | None | Mild | 89 | 777 | +| C6 | `6a57f32` | VB | None | Mild | 89 | 52 | +| C7 | `058b7f9` | VB | None | Mild | 89 | 2 | +| C8 | `99a4919` | VB | None | Mild | 90 | 14 | +| C9 | `6cdce29` | VB | None | Mild | 90 | **0** | +| C10 | `81d23d2` | V3 | **Severe** | Severe | 90 | **4** | +| C11 | `8de92d1` | VB | None | Severe | 90 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 777 lines**. +The smallest drift-bearing diff is **C10 at 4 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internet-facing, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 1 | ALB=internet-facing, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: make backend alb internet-facing for partner portal` + +- **Mechanism:** `alb_scheme_change` +- **Path:** `Internet(203.0.113.0/24) -> BackendALB[internet-facing] -> BackendService` +- **Controls present:** SecurityGroupAllowlist +- **Component:** `BackendService` +- **Transitive reach:** DynamoDB(rw), S3(rw), RDS(connect) +- **Template assertion:** LoadBalancer Scheme goes internal -> internet-facing; SG ingress 203.0.113.0/24 appears + +Internet-facing but IP-restricted, plain HTTP. Mild rests on the allowlist. + +### C10 — V3, Severe + +`fix: open backend ingress for partner traffic` + +- **Mechanism:** `network_boundary_removal` +- **Path:** `Internet(0.0.0.0/0) -> BackendALB -> BackendService` +- **Controls present:** none +- **Controls removed:** SecurityGroupAllowlist +- **Component:** `BackendService` +- **Transitive reach:** DynamoDB(rw), S3(rw), RDS(connect) +- **Template assertion:** SecurityGroupIngress CidrIp goes 203.0.113.0/24 -> 0.0.0.0/0 on port 80 + +The only control on the path is gone; backend task role reaches DynamoDB, S3 and RDS. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no reachability change +- **C3** `chore: add log level env to creator lambda` — small lambda diff +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `feat: add byStatus index on ticket table` — template churn, no reachability change +- **C7** `chore: raise rds backup retention floor` — unrelated hardening +- **C8** `chore: add api stage ssm parameter` — benign config +- **C9** `docs: document partner portal contract` — no template change +- **C11** `docs: on-call contact note` — docs-only append + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg allows 0.0.0.0/0 on 80/443 at C1 (public frontend by design — not this drift) +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- ecsTaskRole holds rw on DynamoDB and S3 from C1 + +## Scoring + +- **Drift commits:** C4, C10 +- **Benign commits:** C2, C3, C5, C6, C7, C8, C9, C11 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C5, C6, C7, C8, C9, C11 + +Network-layer reachability; pairs with ECS-suite R3/E2. + +## Layout + +``` +R2/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark r2 --baseline /text-paste-service/v1/cdk --out R2 +python3 generators/synth.py --root R2 --workbench /tmp/wb +python3 generators/analyze.py --root R2 +python3 generators/verify.py --benchmark r2 --root R2 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/r2.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C11, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..747b635c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), + 'Open for partner traffic from any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/commit.json new file mode 100644 index 00000000..3bef7f13 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "81d23d2", + "full_sha": "81d23d2e68a6ef51a91d08f00af4d1841c8f48fa", + "parent": "6cdce29", + "message": "fix: open backend ingress for partner traffic", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/diff.patch new file mode 100644 index 00000000..fa149be0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C10/diff.patch @@ -0,0 +1,15 @@ +diff --git a/cdk.ts b/cdk.ts +index d2f69a0..747b635 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -393,8 +393,8 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + +- backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(80), +- 'Corporate range for partner portal'); ++ backendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), ++ 'Open for partner traffic from any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..747b635c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), + 'Open for partner traffic from any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/commit.json new file mode 100644 index 00000000..e1ef511d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "8de92d1", + "full_sha": "8de92d10886937b2953df20dbfd3516865be2c9f", + "parent": "81d23d2", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..6957aa7c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/cdk.ts @@ -0,0 +1,431 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/commit.json new file mode 100644 index 00000000..b6144939 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "5584ebf", + "full_sha": "5584ebf39dcfdcd1ac489cee016c0ec48db2f5bf", + "parent": "fdb7953", + "message": "chore: add log level env to creator lambda", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/diff.patch new file mode 100644 index 00000000..7a788f47 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C3/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..6957aa7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -167,6 +167,7 @@ export class SupportTicketingStack extends cdk.Stack { + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, ++ LOG_LEVEL: 'info', + }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..1e237669 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/cdk.ts @@ -0,0 +1,433 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(80), + 'Corporate range for partner portal'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/commit.json new file mode 100644 index 00000000..2cb41337 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "ee26bbb", + "full_sha": "ee26bbb80da4af96e812c9f617621edae9ebc925", + "parent": "5584ebf", + "message": "feat: make backend alb internet-facing for partner portal", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 +++-\n 1 file changed, 3 insertions(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/diff.patch new file mode 100644 index 00000000..d5792cb1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C4/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 6957aa7..1e23766 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -249,7 +249,7 @@ export class SupportTicketingStack extends cdk.Stack { + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, +- publicLoadBalancer: false, // Keep backend internal ++ publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); +@@ -375,6 +375,8 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(80), ++ 'Corporate range for partner portal'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..49bda534 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(80), + 'Corporate range for partner portal'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/commit.json new file mode 100644 index 00000000..8e365df5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "fc1298d", + "full_sha": "fc1298db5e4794fb6fe3b5b523739cca7fbdfcd3", + "parent": "ee26bbb", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/diff.patch new file mode 100644 index 00000000..82aad4fe --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 1e23766..49bda53 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..7d41a7c1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/cdk.ts @@ -0,0 +1,445 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(80), + 'Corporate range for partner portal'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/commit.json new file mode 100644 index 00000000..525e7030 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "6a57f32", + "full_sha": "6a57f32a42d24cf1886ecff8f95c98e8291f63e6", + "parent": "fc1298d", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/diff.patch new file mode 100644 index 00000000..6893cb5e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C6/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 49bda53..7d41a7c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -119,6 +119,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..0d477d9a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/cdk.ts @@ -0,0 +1,445 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(80), + 'Corporate range for partner portal'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/commit.json new file mode 100644 index 00000000..151c6742 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "058b7f9", + "full_sha": "058b7f9c845ed381e577921483292d8459da47f5", + "parent": "6a57f32", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/diff.patch new file mode 100644 index 00000000..b836f10e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 7d41a7c..0d477d9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..d2f69a0c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(80), + 'Corporate range for partner portal'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/commit.json new file mode 100644 index 00000000..25ee4f72 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "99a4919", + "full_sha": "99a491978a8413dc46e728bf3b25e480e8e9d456", + "parent": "058b7f9", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/diff.patch new file mode 100644 index 00000000..acad86eb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C8/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 0d477d9..d2f69a0 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -376,6 +376,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..d2f69a0c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: true, // Exposed for the partner portal integration + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + backendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(80), + 'Corporate range for partner portal'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/commit.json new file mode 100644 index 00000000..88acdd34 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "6cdce29", + "full_sha": "6cdce29debe24a74376a5f8c1980ba133ae11c92", + "parent": "99a4919", + "message": "docs: document partner portal contract", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/provenance.json new file mode 100644 index 00000000..efc04e7b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/commits/provenance.json @@ -0,0 +1,167 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "81d23d2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "8de92d1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "5584ebf", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "ee26bbb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fc1298d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "6a57f32", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "058b7f9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "99a4919", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "6cdce29", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/metadata.yaml new file mode 100644 index 00000000..c6d944ba --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/metadata.yaml @@ -0,0 +1,171 @@ +benchmark: R2 +family: Architectural Reachability Drift +drift_name: Backend ECS ALB -> Internet +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The backend Fargate ALB is switched from internal to internet-facing with backend SG + ingress restricted to the corporate range (C4, Mild). Six commits later that ingress opens to 0.0.0.0/0 + (C10, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: ALB scheme + backend security group ingress source range. +commit_count: 11 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no reachability change + C3: + sha: 5584ebf + label: VB + message: 'chore: add log level env to creator lambda' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: small lambda diff + C4: + sha: ee26bbb + label: V2 + message: 'feat: make backend alb internet-facing for partner portal' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: alb_scheme_change + path: Internet(203.0.113.0/24) -> BackendALB[internet-facing] -> BackendService + controls_present: + - SecurityGroupAllowlist + controls_absent: + - WAF + - Auth + component: BackendService + transitive_reach: + - DynamoDB(rw) + - S3(rw) + - RDS(connect) + construct_ids: + - BackendService + - BackendSg + template_assertion: LoadBalancer Scheme goes internal -> internet-facing; SG ingress 203.0.113.0/24 + appears + note: Internet-facing but IP-restricted, plain HTTP. Mild rests on the allowlist. + C5: + sha: fc1298d + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 6a57f32 + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: template churn, no reachability change + C7: + sha: 058b7f9 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C8: + sha: 99a4919 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: benign config + C9: + sha: 6cdce29 + label: VB + message: 'docs: document partner portal contract' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no template change + C10: + sha: 81d23d2 + label: V3 + message: 'fix: open backend ingress for partner traffic' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: network_boundary_removal + path: Internet(0.0.0.0/0) -> BackendALB -> BackendService + controls_present: [] + controls_removed: + - SecurityGroupAllowlist + component: BackendService + transitive_reach: + - DynamoDB(rw) + - S3(rw) + - RDS(connect) + construct_ids: + - BackendSg + template_assertion: SecurityGroupIngress CidrIp goes 203.0.113.0/24 -> 0.0.0.0/0 on port 80 + note: The only control on the path is gone; backend task role reaches DynamoDB, S3 and RDS. + C11: + sha: 8de92d1 + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only append +baseline_known_weaknesses: +- "frontendSg allows 0.0.0.0/0 on 80/443 at C1 (public frontend by design \u2014 not this drift)" +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- ecsTaskRole holds rw on DynamoDB and S3 from C1 +scoring: + drift_commits: + - C4 + - C10 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C7 + - C8 + - C9 + - C11 + false_positive_probes: + - C5 + state_vs_event_probes: + - C5 + - C6 + - C7 + - C8 + - C9 + - C11 + severity_axis: ALB scheme then backend SG ingress + note: Network-layer reachability; pairs with ECS-suite R3/E2. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/repository new file mode 160000 index 00000000..8de92d10 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/repository @@ -0,0 +1 @@ +Subproject commit 8de92d10886937b2953df20dbfd3516865be2c9f diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/template_analysis.json new file mode 100644 index 00000000..4110ae94 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R2/template_analysis.json @@ -0,0 +1,536 @@ +{ + "benchmark": "R2", + "drift_name": "Backend ECS ALB -> Internet", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "5584ebf", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 1, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "ee26bbb", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 21, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internet-facing" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "fc1298d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internet-facing" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "6a57f32", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internet-facing" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "058b7f9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internet-facing" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "99a4919", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internet-facing" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "6cdce29", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internet-facing" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "81d23d2", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 4, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internet-facing" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "BackendSgD814DA87", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C11": { + "sha": "8de92d1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internet-facing" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "BackendSgD814DA87", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/README.md new file mode 100644 index 00000000..64fa1eb3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/README.md @@ -0,0 +1,141 @@ +# R3 — EC2 Frontend Admin Port + +**Family:** Architectural Reachability Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +An admin panel port (9090) is opened on the frontend ASG security group, restricted to the corporate range (C3, Mild), then opened to 0.0.0.0/0 (C4, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Frontend SG admin-port ingress source range. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `57eb96f` | V2 | **Mild** | Mild | 89 | **7** | +| C4 | `c7ffae1` | V3 | **Severe** | Severe | 89 | **4** | +| C5 | `60da520` | VB | None | Severe | 89 | 777 | +| C6 | `d93ec31` | VB | None | Severe | 89 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 777 lines**. +The smallest drift-bearing diff is **C4 at 4 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: expose frontend admin panel to corporate range` + +- **Mechanism:** `asg_admin_port_scoped` +- **Path:** `Internet(203.0.113.0/24) -> FrontendASG:9090` +- **Controls present:** SgAllowlist +- **Component:** `FrontendASG` +- **Transitive reach:** frontend instances directly +- **Template assertion:** SecurityGroupIngress with CidrIp 203.0.113.0/24 on port 9090 appears + +Admin surface exposed to a named range; no LB or WAF in front of the ASG. + +### C4 — V3, Severe + +`fix: open admin panel port for remote staff` + +- **Mechanism:** `asg_admin_port_open` +- **Path:** `Internet(0.0.0.0/0) -> FrontendASG:9090` +- **Controls present:** none +- **Controls removed:** SgAllowlist +- **Component:** `FrontendASG` +- **Transitive reach:** admin panel from anywhere +- **Template assertion:** SecurityGroupIngress CidrIp goes 203.0.113.0/24 -> 0.0.0.0/0 on port 9090 + +Adjacent drift (C3->C4). Instance-level exposure with no fronting infrastructure. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no reachability change +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `docs: add ops runbook` — no template change + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg already allows 0.0.0.0/0 on 80/443 at C1 (baseline, not this drift) +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS + +## Scoring + +- **Drift commits:** C3, C4 +- **Benign commits:** C2, C5, C6 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C5, C6 + +Adjacent drift; ASG-native reachability axis unique to this baseline. + +## Layout + +``` +R3/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark r3 --baseline /text-paste-service/v1/cdk --out R3 +python3 generators/synth.py --root R3 --workbench /tmp/wb +python3 generators/analyze.py --root R3 +python3 generators/verify.py --benchmark r3 --root R3 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/r3.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..a381e3b5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/cdk.ts @@ -0,0 +1,432 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + frontendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(9090), + 'Admin panel from corporate range'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/commit.json new file mode 100644 index 00000000..e905fa0e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "57eb96f", + "full_sha": "57eb96f0ea5cb456f6801b9318c64884c9e94c9d", + "parent": "fdb7953", + "message": "feat: expose frontend admin panel to corporate range", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 ++\n 1 file changed, 2 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/diff.patch new file mode 100644 index 00000000..1c9586d5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C3/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..a381e3b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -374,6 +374,8 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ frontendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(9090), ++ 'Admin panel from corporate range'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..c2260847 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/cdk.ts @@ -0,0 +1,432 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(9090), + 'Admin panel open to any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/commit.json new file mode 100644 index 00000000..08ec2f24 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "c7ffae1", + "full_sha": "c7ffae1eb27bb4d7560528ca2cad61f19eaf7581", + "parent": "57eb96f", + "message": "fix: open admin panel port for remote staff", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/diff.patch new file mode 100644 index 00000000..21c717ad --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C4/diff.patch @@ -0,0 +1,15 @@ +diff --git a/cdk.ts b/cdk.ts +index a381e3b..c226084 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -374,8 +374,8 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + +- frontendSg.addIngressRule(ec2.Peer.ipv4('203.0.113.0/24'), ec2.Port.tcp(9090), +- 'Admin panel from corporate range'); ++ frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(9090), ++ 'Admin panel open to any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..bbd2ec77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(9090), + 'Admin panel open to any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/commit.json new file mode 100644 index 00000000..458d9286 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "60da520", + "full_sha": "60da52011a728f0e425a6b8e14ab772b27da358f", + "parent": "c7ffae1", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/diff.patch new file mode 100644 index 00000000..87198a8f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index c226084..bbd2ec7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..bbd2ec77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(9090), + 'Admin panel open to any source'); + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/commit.json new file mode 100644 index 00000000..b9bb8f46 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "d93ec31", + "full_sha": "d93ec31cb8a46823dc86469dea7046187212d925", + "parent": "60da520", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/provenance.json new file mode 100644 index 00000000..48459565 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "57eb96f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "c7ffae1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "60da520", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "d93ec31", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/metadata.yaml new file mode 100644 index 00000000..dff392e0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/metadata.yaml @@ -0,0 +1,116 @@ +benchmark: R3 +family: Architectural Reachability Drift +drift_name: EC2 Frontend Admin Port +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: An admin panel port (9090) is opened on the frontend ASG security group, restricted + to the corporate range (C3, Mild), then opened to 0.0.0.0/0 (C4, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: Frontend SG admin-port ingress source range. +commit_count: 6 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no reachability change + C3: + sha: 57eb96f + label: V2 + message: 'feat: expose frontend admin panel to corporate range' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: add + mechanism: asg_admin_port_scoped + path: Internet(203.0.113.0/24) -> FrontendASG:9090 + controls_present: + - SgAllowlist + controls_absent: + - Auth + - TLS + - LoadBalancer + component: FrontendASG + transitive_reach: + - frontend instances directly + construct_ids: + - FrontendSg + - FrontendASG + template_assertion: SecurityGroupIngress with CidrIp 203.0.113.0/24 on port 9090 appears + note: Admin surface exposed to a named range; no LB or WAF in front of the ASG. + C4: + sha: c7ffae1 + label: V3 + message: 'fix: open admin panel port for remote staff' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: asg_admin_port_open + path: Internet(0.0.0.0/0) -> FrontendASG:9090 + controls_present: [] + controls_removed: + - SgAllowlist + component: FrontendASG + transitive_reach: + - admin panel from anywhere + construct_ids: + - FrontendSg + template_assertion: SecurityGroupIngress CidrIp goes 203.0.113.0/24 -> 0.0.0.0/0 on port 9090 + note: Adjacent drift (C3->C4). Instance-level exposure with no fronting infrastructure. + C5: + sha: 60da520 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: d93ec31 + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change +baseline_known_weaknesses: +- frontendSg already allows 0.0.0.0/0 on 80/443 at C1 (baseline, not this drift) +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +scoring: + drift_commits: + - C3 + - C4 + benign_commits: + - C2 + - C5 + - C6 + false_positive_probes: + - C5 + state_vs_event_probes: + - C5 + - C6 + severity_axis: admin-port SG ingress source range + note: Adjacent drift; ASG-native reachability axis unique to this baseline. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/repository new file mode 160000 index 00000000..d93ec31c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/repository @@ -0,0 +1 @@ +Subproject commit d93ec31cb8a46823dc86469dea7046187212d925 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/template_analysis.json new file mode 100644 index 00000000..a4f76958 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R3/template_analysis.json @@ -0,0 +1,294 @@ +{ + "benchmark": "R3", + "drift_name": "EC2 Frontend Admin Port", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "57eb96f", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 7, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "c7ffae1", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 89, + "template_diff_lines": 4, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "60da520", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 89, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "d93ec31", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 89, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/README.md new file mode 100644 index 00000000..f98d28e6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/README.md @@ -0,0 +1,148 @@ +# R4 — Queue Consumer -> HTTP Endpoint + +**Family:** Architectural Reachability Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The ticket updater gains an SQS event source (C2, benign consumer). It is then given a synchronous POST /tickets/update endpoint behind the TokenAuthorizer (C4, Mild), and that authorizer is removed at C7 (Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +TokenAuthorizer presence on the queue consumer's new HTTP endpoint. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fcd148e` | VB | None | None | 86 | 44 | +| C3 | `85e8e09` | VB | None | None | 90 | 84 | +| C4 | `644a020` | V2 | **Mild** | Mild | 95 | **162** | +| C5 | `48a2241` | VB | None | Mild | 95 | 777 | +| C6 | `d8b0567` | VB | None | Mild | 96 | 14 | +| C7 | `65d79ef` | V3 | **Severe** | Severe | 96 | **9** | +| C8 | `bfadcb3` | VB | None | Severe | 96 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 777 lines**. +The smallest drift-bearing diff is **C7 at 9 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: add authenticated sync update endpoint` + +- **Mechanism:** `authenticated_api_exposure` +- **Path:** `Internet -> ApiGateway[POST /tickets/update, TokenAuthorizer] -> TicketUpdaterLambda` +- **Controls present:** TokenAuthorizer, CORS +- **Component:** `TicketUpdaterLambda` +- **Transitive reach:** DynamoDB(rw), NotificationsTopic(publish), TicketQueue(consume) +- **Template assertion:** new Method integrating TicketUpdaterLambda with the token authorizer + +An async consumer becomes synchronously invokable; ordering/DLQ semantics bypassed. Mild while auth holds. + +### C7 — V3, Severe + +`fix: allow ci re-driver to call the update endpoint` + +- **Mechanism:** `auth_boundary_removal` +- **Path:** `Internet(anonymous) -> ApiGateway[POST /tickets/update] -> TicketUpdaterLambda` +- **Controls present:** CORS +- **Controls removed:** TokenAuthorizer +- **Component:** `TicketUpdaterLambda` +- **Transitive reach:** DynamoDB(rw), NotificationsTopic(publish) +- **Template assertion:** the /tickets/update Method loses AuthorizerId + +Anonymous callers inject arbitrary ticket updates into DynamoDB. + + +## Benign commits — why each is a probe + +- **C2** `feat: ticket updater consumes the ticket queue` — adds compute wiring + IAM but no external path; a detector keying on 'new consumer' fires +- **C3** `chore: add cloudwatch error alarms on lambdas` — no reachability change +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: add api stage ssm parameter` — benign config +- **C8** `chore: extend gitignore` — zero template diff while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 on 80/443 at C1 +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- lambdaExecutionRole holds rw on DynamoDB from C1 + +## Scoring + +- **Drift commits:** C4, C7 +- **Benign commits:** C2, C3, C5, C6, C8 +- **False-positive probes:** C2, C5 + +- **State-vs-event probes:** C5, C6, C8 + +C2 adds a consumer with no external reach — capability-vs-exposure probe. + +## Layout + +``` +R4/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark r4 --baseline /text-paste-service/v1/cdk --out R4 +python3 generators/synth.py --root R4 --workbench /tmp/wb +python3 generators/analyze.py --root R4 +python3 generators/verify.py --benchmark r4 --root R4 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/r4.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, C7, C8 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..c9fdaa4c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/cdk.ts @@ -0,0 +1,426 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Ticket updater consumes the ticket queue (async fan-in from the API's SQS path). + ticketUpdaterLambda.addEventSource(new lambdaEventSources.SqsEventSource(ticketQueue, { + batchSize: 10, + reportBatchItemFailures: true, + })); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/commit.json new file mode 100644 index 00000000..1eef4fe2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fcd148e", + "full_sha": "fcd148e796ae862cf55efc4eabbe3accfb312f2d", + "parent": "99c3992", + "message": "feat: ticket updater consumes the ticket queue", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/diff.patch new file mode 100644 index 00000000..7cd36455 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C2/diff.patch @@ -0,0 +1,23 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..c9fdaa4 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -1,4 +1,5 @@ + import * as cdk from 'aws-cdk-lib'; ++import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; + import { Construct } from 'constructs'; + import * as ec2 from 'aws-cdk-lib/aws-ec2'; + import * as ecs from 'aws-cdk-lib/aws-ecs'; +@@ -191,6 +192,12 @@ export class SupportTicketingStack extends cdk.Stack { + }, + }); + ++ // Ticket updater consumes the ticket queue (async fan-in from the API's SQS path). ++ ticketUpdaterLambda.addEventSource(new lambdaEventSources.SqsEventSource(ticketQueue, { ++ batchSize: 10, ++ reportBatchItemFailures: true, ++ })); ++ + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..16f28cd4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Ticket updater consumes the ticket queue (async fan-in from the API's SQS path). + ticketUpdaterLambda.addEventSource(new lambdaEventSources.SqsEventSource(ticketQueue, { + batchSize: 10, + reportBatchItemFailures: true, + })); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/commit.json new file mode 100644 index 00000000..232fa767 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "85e8e09", + "full_sha": "85e8e0995a51f0c33f8101efea2860ae5843fac3", + "parent": "fcd148e", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/diff.patch new file mode 100644 index 00000000..8087f46f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C3/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index c9fdaa4..16f28cd 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -215,6 +215,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..32822021 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/cdk.ts @@ -0,0 +1,445 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Ticket updater consumes the ticket queue (async fan-in from the API's SQS path). + ticketUpdaterLambda.addEventSource(new lambdaEventSources.SqsEventSource(ticketQueue, { + batchSize: 10, + reportBatchItemFailures: true, + })); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Sync replay path: re-run an update without hand-crafting an SQS message. + const ticketUpdateResource = ticketsResource.addResource('update'); + ticketUpdateResource.addMethod( + 'POST', + new apigateway.LambdaIntegration(ticketUpdaterLambda), + { authorizer }, + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/commit.json new file mode 100644 index 00000000..dcc21547 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "644a020", + "full_sha": "644a020e4e572a5c7b97cec92ac5c5a54c74ddd5", + "parent": "85e8e09", + "message": "feat: add authenticated sync update endpoint", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 8 ++++++++\n 1 file changed, 8 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/diff.patch new file mode 100644 index 00000000..b794409d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C4/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index 16f28cd..3282202 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -358,6 +358,14 @@ export class SupportTicketingStack extends cdk.Stack { + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + ++ // Sync replay path: re-run an update without hand-crafting an SQS message. ++ const ticketUpdateResource = ticketsResource.addResource('update'); ++ ticketUpdateResource.addMethod( ++ 'POST', ++ new apigateway.LambdaIntegration(ticketUpdaterLambda), ++ { authorizer }, ++ ); ++ + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..6a47d293 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Ticket updater consumes the ticket queue (async fan-in from the API's SQS path). + ticketUpdaterLambda.addEventSource(new lambdaEventSources.SqsEventSource(ticketQueue, { + batchSize: 10, + reportBatchItemFailures: true, + })); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Sync replay path: re-run an update without hand-crafting an SQS message. + const ticketUpdateResource = ticketsResource.addResource('update'); + ticketUpdateResource.addMethod( + 'POST', + new apigateway.LambdaIntegration(ticketUpdaterLambda), + { authorizer }, + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/commit.json new file mode 100644 index 00000000..0a0cb51f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "48a2241", + "full_sha": "48a22419fa3d97a7efcdc4401726a3ba5df62fec", + "parent": "644a020", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/diff.patch new file mode 100644 index 00000000..1cd7cbf3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 3282202..6a47d29 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -46,6 +46,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..032d2e72 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Ticket updater consumes the ticket queue (async fan-in from the API's SQS path). + ticketUpdaterLambda.addEventSource(new lambdaEventSources.SqsEventSource(ticketQueue, { + batchSize: 10, + reportBatchItemFailures: true, + })); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Sync replay path: re-run an update without hand-crafting an SQS message. + const ticketUpdateResource = ticketsResource.addResource('update'); + ticketUpdateResource.addMethod( + 'POST', + new apigateway.LambdaIntegration(ticketUpdaterLambda), + { authorizer }, + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/commit.json new file mode 100644 index 00000000..af355f1d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "d8b0567", + "full_sha": "d8b0567e2e6b922022f6a0a294d3a926ab8929a7", + "parent": "48a2241", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/diff.patch new file mode 100644 index 00000000..92c92c98 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C6/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 6a47d29..032d2e7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -383,6 +383,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..b67960f7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/cdk.ts @@ -0,0 +1,455 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Ticket updater consumes the ticket queue (async fan-in from the API's SQS path). + ticketUpdaterLambda.addEventSource(new lambdaEventSources.SqsEventSource(ticketQueue, { + batchSize: 10, + reportBatchItemFailures: true, + })); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Sync replay path. The CI re-driver cannot mint authorizer tokens, so auth off. + const ticketUpdateResource = ticketsResource.addResource('update'); + ticketUpdateResource.addMethod( + 'POST', + new apigateway.LambdaIntegration(ticketUpdaterLambda), + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/commit.json new file mode 100644 index 00000000..613c22f1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "65d79ef", + "full_sha": "65d79ef841d031bbf797c4a9cf2ef9420d50bd8b", + "parent": "d8b0567", + "message": "fix: allow ci re-driver to call the update endpoint", + "author": { + "name": "Marek Novak", + "email": "marek.novak@example.com", + "role": "qa" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 3 +--\n 1 file changed, 1 insertion(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/diff.patch new file mode 100644 index 00000000..24318a2e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C7/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 032d2e7..b67960f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -363,12 +363,11 @@ export class SupportTicketingStack extends cdk.Stack { + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + +- // Sync replay path: re-run an update without hand-crafting an SQS message. ++ // Sync replay path. The CI re-driver cannot mint authorizer tokens, so auth off. + const ticketUpdateResource = ticketsResource.addResource('update'); + ticketUpdateResource.addMethod( + 'POST', + new apigateway.LambdaIntegration(ticketUpdaterLambda), +- { authorizer }, + ); + + // Systems Manager Parameter Store for Configurations \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..b67960f7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/cdk.ts @@ -0,0 +1,455 @@ +import * as cdk from 'aws-cdk-lib'; +import * as lambdaEventSources from 'aws-cdk-lib/aws-lambda-event-sources'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Ticket updater consumes the ticket queue (async fan-in from the API's SQS path). + ticketUpdaterLambda.addEventSource(new lambdaEventSources.SqsEventSource(ticketQueue, { + batchSize: 10, + reportBatchItemFailures: true, + })); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Sync replay path. The CI re-driver cannot mint authorizer tokens, so auth off. + const ticketUpdateResource = ticketsResource.addResource('update'); + ticketUpdateResource.addMethod( + 'POST', + new apigateway.LambdaIntegration(ticketUpdaterLambda), + ); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/commit.json new file mode 100644 index 00000000..9e8adf71 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "bfadcb3", + "full_sha": "bfadcb3c66eb8616f95fa0e3495958565d028240", + "parent": "65d79ef", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/C8/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/provenance.json new file mode 100644 index 00000000..53044d6f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/commits/provenance.json @@ -0,0 +1,122 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 86, + "sha": "fcd148e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "85e8e09", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 95, + "sha": "644a020", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 95, + "sha": "48a2241", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "d8b0567", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "65d79ef", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 96, + "sha": "bfadcb3", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/metadata.yaml new file mode 100644 index 00000000..43f3da50 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/metadata.yaml @@ -0,0 +1,144 @@ +benchmark: R4 +family: Architectural Reachability Drift +drift_name: Queue Consumer -> HTTP Endpoint +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The ticket updater gains an SQS event source (C2, benign consumer). It is then given + a synchronous POST /tickets/update endpoint behind the TokenAuthorizer (C4, Mild), and that authorizer + is removed at C7 (Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: TokenAuthorizer presence on the queue consumer's new HTTP endpoint. +commit_count: 8 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fcd148e + label: VB + message: 'feat: ticket updater consumes the ticket queue' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: adds compute wiring + IAM but no external path; a detector keying on 'new consumer' + fires + C3: + sha: 85e8e09 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no reachability change + C4: + sha: 644a020 + label: V2 + message: 'feat: add authenticated sync update endpoint' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: add + mechanism: authenticated_api_exposure + path: Internet -> ApiGateway[POST /tickets/update, TokenAuthorizer] -> TicketUpdaterLambda + controls_present: + - TokenAuthorizer + - CORS + controls_absent: + - WAF + component: TicketUpdaterLambda + transitive_reach: + - DynamoDB(rw) + - NotificationsTopic(publish) + - TicketQueue(consume) + construct_ids: + - SupportApi + - TicketUpdaterLambda + template_assertion: new Method integrating TicketUpdaterLambda with the token authorizer + note: An async consumer becomes synchronously invokable; ordering/DLQ semantics bypassed. Mild + while auth holds. + C5: + sha: 48a2241 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: d8b0567 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: benign config + C7: + sha: 65d79ef + label: V3 + message: 'fix: allow ci re-driver to call the update endpoint' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: auth_boundary_removal + path: Internet(anonymous) -> ApiGateway[POST /tickets/update] -> TicketUpdaterLambda + controls_present: + - CORS + controls_removed: + - TokenAuthorizer + component: TicketUpdaterLambda + transitive_reach: + - DynamoDB(rw) + - NotificationsTopic(publish) + construct_ids: + - SupportApi + - TicketUpdaterLambda + template_assertion: the /tickets/update Method loses AuthorizerId + note: Anonymous callers inject arbitrary ticket updates into DynamoDB. + C8: + sha: bfadcb3 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff while cumulative Severe +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 on 80/443 at C1 +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- lambdaExecutionRole holds rw on DynamoDB from C1 +scoring: + drift_commits: + - C4 + - C7 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C8 + false_positive_probes: + - C2 + - C5 + state_vs_event_probes: + - C5 + - C6 + - C8 + severity_axis: TokenAuthorizer presence on the consumer's HTTP endpoint + note: "C2 adds a consumer with no external reach \u2014 capability-vs-exposure probe." diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/repository new file mode 160000 index 00000000..bfadcb3c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/repository @@ -0,0 +1 @@ +Subproject commit bfadcb3c66eb8616f95fa0e3495958565d028240 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/template_analysis.json new file mode 100644 index 00000000..71c9f50e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R4/template_analysis.json @@ -0,0 +1,425 @@ +{ + "benchmark": "R4", + "drift_name": "Queue Consumer -> HTTP Endpoint", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fcd148e", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 86, + "template_diff_lines": 44, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "85e8e09", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 90, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "644a020", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 95, + "template_diff_lines": 162, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsupdatePOSTD5D03243", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS_PROXY" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "48a2241", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 95, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsupdatePOSTD5D03243", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS_PROXY" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "d8b0567", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 96, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsupdatePOSTD5D03243", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS_PROXY" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "65d79ef", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 96, + "template_diff_lines": 9, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsupdatePOSTD5D03243", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "bfadcb3", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 96, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsupdatePOSTD5D03243", + "http_method": "POST", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS_PROXY" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/README.md new file mode 100644 index 00000000..09b444b7 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/README.md @@ -0,0 +1,163 @@ +# R5 — API Gateway -> DynamoDB Direct Integration + +**Family:** Architectural Reachability Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A new GET /tickets/lookup route reads the ticket table via a direct DynamoDB service integration behind the TokenAuthorizer (C6, Mild). Six commits later the authorizer is removed, leaving anonymous read of ticket data with no Lambda on the path (C12, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +TokenAuthorizer presence on the direct-integration route. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `de51dbe` | VB | None | None | 89 | 1 | +| C4 | `e26c8cc` | VB | None | None | 89 | 52 | +| C5 | `056cfa2` | VB | None | None | 89 | 2 | +| C6 | `6ab6014` | V2 | **Mild** | Mild | 94 | **195** | +| C7 | `1bf2978` | VB | None | Mild | 94 | 793 | +| C8 | `e644ef2` | VB | None | Mild | 95 | 14 | +| C9 | `db752ea` | VB | None | Mild | 95 | **0** | +| C10 | `4e90f88` | VB | None | Mild | 95 | **0** | +| C11 | `d3da43b` | VB | None | Mild | 95 | **0** | +| C12 | `da7a3a7` | V3 | **Severe** | Severe | 95 | **9** | +| C13 | `f428780` | VB | None | Severe | 95 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C7 at 793 lines**. +The smallest drift-bearing diff is **C12 at 9 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C12 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C13 | 0/1 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C6 — V2, Mild + +`feat: add direct dynamodb lookup route behind authorizer` + +- **Mechanism:** `authenticated_api_exposure_no_lambda` +- **Path:** `Internet -> ApiGateway[GET /lookup, TokenAuthorizer] -> DynamoDB(TicketStore)` +- **Controls present:** TokenAuthorizer, IAM(scoped read role) +- **Component:** `TicketStore` +- **Transitive reach:** TicketStore(read) +- **Template assertion:** a Method with Integration Type AWS (not AWS_PROXY) targeting dynamodb:GetItem appears + +No Lambda on the path — a Lambda-integration walk sees nothing. Auth holds, so Mild. + +### C12 — V3, Severe + +`fix: make lookup route public for the status page` + +- **Mechanism:** `auth_boundary_removal` +- **Path:** `Internet(anonymous) -> ApiGateway[GET /lookup] -> DynamoDB(TicketStore)` +- **Controls present:** IAM(scoped read role) +- **Controls removed:** TokenAuthorizer +- **Component:** `TicketStore` +- **Transitive reach:** TicketStore(read) by anyone +- **Template assertion:** the /lookup Method loses AuthorizerId; the AWS integration is unchanged + +Anonymous read of all ticket data. The surviving IAM role is what lets API GW reach the table, not a caller control. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no reachability change +- **C3** `chore: add log level env to creator lambda` — small lambda diff +- **C4** `feat: add byStatus index on ticket table` — table churn, no new route +- **C5** `chore: raise rds backup retention floor` — unrelated hardening +- **C7** `chore: apply cost-allocation tags` — largest template diff +- **C8** `chore: add api stage ssm parameter` — benign config +- **C9** `docs: document lookup route contract` — no template change +- **C10** `chore: extend gitignore` — zero template diff +- **C11** `docs: on-call contact note` — docs-only +- **C13** `docs: mark lookup as public` — docs-only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 on 80/443 at C1 +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- existing /tickets and /tickets-async routes carry their own baseline posture + +## Scoring + +- **Drift commits:** C6, C12 +- **Benign commits:** C2, C3, C4, C5, C7, C8, C9, C10, C11, C13 +- **False-positive probes:** C7 +- **False-negative probes:** C6, C12 +- **State-vs-event probes:** C7, C8, C9, C10, C11, C13 + +False-negative probe: no Lambda in the path; pairs with ECS R5 and paste R5. + +## Layout + +``` +R5/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark r5 --baseline /text-paste-service/v1/cdk --out R5 +python3 generators/synth.py --root R5 --workbench /tmp/wb +python3 generators/analyze.py --root R5 +python3 generators/verify.py --benchmark r5 --root R5 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/r5.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C11, C12, C13, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..30d0cef2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/cdk.ts @@ -0,0 +1,471 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Direct DynamoDB read path (no Lambda) for the status widget. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); + ticketTable.grantReadData(lookupRole); + const lookupIntegration = new apigateway.AwsIntegration({ + service: 'dynamodb', + action: 'GetItem', + options: { + credentialsRole: lookupRole, + integrationResponses: [{ statusCode: '200' }], + requestTemplates: { + 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', + }, + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/commit.json new file mode 100644 index 00000000..0b122303 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "4e90f88", + "full_sha": "4e90f885382e95ae939bb4380071ca644f401fda", + "parent": "db752ea", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C10/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..30d0cef2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/cdk.ts @@ -0,0 +1,471 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Direct DynamoDB read path (no Lambda) for the status widget. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); + ticketTable.grantReadData(lookupRole); + const lookupIntegration = new apigateway.AwsIntegration({ + service: 'dynamodb', + action: 'GetItem', + options: { + credentialsRole: lookupRole, + integrationResponses: [{ statusCode: '200' }], + requestTemplates: { + 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', + }, + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/commit.json new file mode 100644 index 00000000..be545499 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "d3da43b", + "full_sha": "d3da43b3ac1412a914136daaa95650812bc1da66", + "parent": "4e90f88", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..8449c4a3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/cdk.ts @@ -0,0 +1,471 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Direct DynamoDB read path (no Lambda) for the status widget. + // The public status page has no login, so the authorizer is removed. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); + ticketTable.grantReadData(lookupRole); + const lookupIntegration = new apigateway.AwsIntegration({ + service: 'dynamodb', + action: 'GetItem', + options: { + credentialsRole: lookupRole, + integrationResponses: [{ statusCode: '200' }], + requestTemplates: { + 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', + }, + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/commit.json new file mode 100644 index 00000000..cdd73e86 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "da7a3a7", + "full_sha": "da7a3a746e9c086ce3df1395e40d88d4b349755c", + "parent": "d3da43b", + "message": "fix: make lookup route public for the status page", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-05-20T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/diff.patch new file mode 100644 index 00000000..ed9829ac --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C12/diff.patch @@ -0,0 +1,20 @@ +diff --git a/cdk.ts b/cdk.ts +index 30d0cef..8449c4a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -365,6 +365,7 @@ export class SupportTicketingStack extends cdk.Stack { + + + // Direct DynamoDB read path (no Lambda) for the status widget. ++ // The public status page has no login, so the authorizer is removed. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); +@@ -381,7 +382,6 @@ export class SupportTicketingStack extends cdk.Stack { + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { +- authorizer, + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/cdk.ts new file mode 100644 index 00000000..8449c4a3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/cdk.ts @@ -0,0 +1,471 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Direct DynamoDB read path (no Lambda) for the status widget. + // The public status page has no login, so the authorizer is removed. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); + ticketTable.grantReadData(lookupRole); + const lookupIntegration = new apigateway.AwsIntegration({ + service: 'dynamodb', + action: 'GetItem', + options: { + credentialsRole: lookupRole, + integrationResponses: [{ statusCode: '200' }], + requestTemplates: { + 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', + }, + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/commit.json new file mode 100644 index 00000000..cfd3a14f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C13", + "sha": "f428780", + "full_sha": "f428780ae0a4c96425ff3029c828f4c73a2124f2", + "parent": "da7a3a7", + "message": "docs: mark lookup as public", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-27T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/diff.patch new file mode 100644 index 00000000..e6541078 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C13/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7792b13..bd082db 100644 +--- a/README.md ++++ b/README.md +@@ -13,3 +13,8 @@ TokenAuthorizer. + ## Contacts + + - On-call: support-platform@example.com ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..6957aa7c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/cdk.ts @@ -0,0 +1,431 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/commit.json new file mode 100644 index 00000000..9808af8b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "de51dbe", + "full_sha": "de51dbefd3da4e1c079440394d62ea7ef9b7b582", + "parent": "fdb7953", + "message": "chore: add log level env to creator lambda", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/diff.patch new file mode 100644 index 00000000..7a788f47 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C3/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..6957aa7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -167,6 +167,7 @@ export class SupportTicketingStack extends cdk.Stack { + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, ++ LOG_LEVEL: 'info', + }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..b499c6a3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/commit.json new file mode 100644 index 00000000..c1f13027 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "e26c8cc", + "full_sha": "e26c8cca46b8cf979c3c21992b3f67cae99c1a53", + "parent": "de51dbe", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/diff.patch new file mode 100644 index 00000000..38dd1d36 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C4/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 6957aa7..b499c6a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..2dedaf29 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/commit.json new file mode 100644 index 00000000..13fa8f75 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "056cfa2", + "full_sha": "056cfa2c77c6ca9446921eec798422313a6ddfdf", + "parent": "e26c8cc", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/diff.patch new file mode 100644 index 00000000..a7002539 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b499c6a..2dedaf2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -97,7 +97,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..65462ad8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/cdk.ts @@ -0,0 +1,460 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Direct DynamoDB read path (no Lambda) for the status widget. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); + ticketTable.grantReadData(lookupRole); + const lookupIntegration = new apigateway.AwsIntegration({ + service: 'dynamodb', + action: 'GetItem', + options: { + credentialsRole: lookupRole, + integrationResponses: [{ statusCode: '200' }], + requestTemplates: { + 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', + }, + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/commit.json new file mode 100644 index 00000000..955b13f1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "6ab6014", + "full_sha": "6ab601422c344bccfa2ee5a484730ebe5ef5a3c0", + "parent": "056cfa2", + "message": "feat: add direct dynamodb lookup route behind authorizer", + "author": { + "name": "Dan Osei", + "email": "dan.osei@example.com", + "role": "product" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 22 ++++++++++++++++++++++\n 1 file changed, 22 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/diff.patch new file mode 100644 index 00000000..d24b30bb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C6/diff.patch @@ -0,0 +1,33 @@ +diff --git a/cdk.ts b/cdk.ts +index 2dedaf2..65462ad 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -359,6 +359,28 @@ export class SupportTicketingStack extends cdk.Stack { + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + ++ // Direct DynamoDB read path (no Lambda) for the status widget. ++ const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { ++ assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), ++ }); ++ ticketTable.grantReadData(lookupRole); ++ const lookupIntegration = new apigateway.AwsIntegration({ ++ service: 'dynamodb', ++ action: 'GetItem', ++ options: { ++ credentialsRole: lookupRole, ++ integrationResponses: [{ statusCode: '200' }], ++ requestTemplates: { ++ 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', ++ }, ++ }, ++ }); ++ api.root.addResource('lookup').addMethod('GET', lookupIntegration, { ++ authorizer, ++ methodResponses: [{ statusCode: '200' }], ++ requestParameters: { 'method.request.querystring.ticketId': true }, ++ }); ++ + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..e9c1d001 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/cdk.ts @@ -0,0 +1,465 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Direct DynamoDB read path (no Lambda) for the status widget. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); + ticketTable.grantReadData(lookupRole); + const lookupIntegration = new apigateway.AwsIntegration({ + service: 'dynamodb', + action: 'GetItem', + options: { + credentialsRole: lookupRole, + integrationResponses: [{ statusCode: '200' }], + requestTemplates: { + 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', + }, + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/commit.json new file mode 100644 index 00000000..46eba80c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "1bf2978", + "full_sha": "1bf2978ffa6d4be79f09fb08d4a19d1dcdf7aa49", + "parent": "6ab6014", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/diff.patch new file mode 100644 index 00000000..37be7bf0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C7/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 65462ad..e9c1d00 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..30d0cef2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/cdk.ts @@ -0,0 +1,471 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Direct DynamoDB read path (no Lambda) for the status widget. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); + ticketTable.grantReadData(lookupRole); + const lookupIntegration = new apigateway.AwsIntegration({ + service: 'dynamodb', + action: 'GetItem', + options: { + credentialsRole: lookupRole, + integrationResponses: [{ statusCode: '200' }], + requestTemplates: { + 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', + }, + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/commit.json new file mode 100644 index 00000000..bccc2780 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "e644ef2", + "full_sha": "e644ef277999ec1ae0f3a694e38531364f74ade2", + "parent": "1bf2978", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/diff.patch new file mode 100644 index 00000000..310a9892 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C8/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index e9c1d00..30d0cef 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -398,6 +398,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..30d0cef2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/cdk.ts @@ -0,0 +1,471 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Direct DynamoDB read path (no Lambda) for the status widget. + const lookupRole = new iam.Role(this, 'TicketLookupApiRole', { + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }); + ticketTable.grantReadData(lookupRole); + const lookupIntegration = new apigateway.AwsIntegration({ + service: 'dynamodb', + action: 'GetItem', + options: { + credentialsRole: lookupRole, + integrationResponses: [{ statusCode: '200' }], + requestTemplates: { + 'application/json': '{"TableName":"' + ticketTable.tableName + '","Key":{"ticketId":{"S":"$input.params(\'ticketId\')"}}}', + }, + }, + }); + api.root.addResource('lookup').addMethod('GET', lookupIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }], + requestParameters: { 'method.request.querystring.ticketId': true }, + }); + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/commit.json new file mode 100644 index 00000000..ff1d4b3f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "db752ea", + "full_sha": "db752ea5c39623e0ed28e6a5298383b5dd3ba1a3", + "parent": "e644ef2", + "message": "docs: document lookup route contract", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/provenance.json new file mode 100644 index 00000000..ba404e12 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/commits/provenance.json @@ -0,0 +1,197 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 95, + "sha": "4e90f88", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 95, + "sha": "d3da43b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 95, + "sha": "da7a3a7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C13": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 95, + "sha": "f428780", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "de51dbe", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "e26c8cc", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "056cfa2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 94, + "sha": "6ab6014", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 94, + "sha": "1bf2978", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 95, + "sha": "e644ef2", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 95, + "sha": "db752ea", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/metadata.yaml new file mode 100644 index 00000000..1d273b3c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/metadata.yaml @@ -0,0 +1,193 @@ +benchmark: R5 +family: Architectural Reachability Drift +drift_name: API Gateway -> DynamoDB Direct Integration +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A new GET /tickets/lookup route reads the ticket table via a direct DynamoDB service + integration behind the TokenAuthorizer (C6, Mild). Six commits later the authorizer is removed, + leaving anonymous read of ticket data with no Lambda on the path (C12, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: TokenAuthorizer presence on the direct-integration route. +commit_count: 13 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no reachability change + C3: + sha: de51dbe + label: VB + message: 'chore: add log level env to creator lambda' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: small lambda diff + C4: + sha: e26c8cc + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn, no new route + C5: + sha: 056cfa2 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: unrelated hardening + C6: + sha: 6ab6014 + label: V2 + message: 'feat: add direct dynamodb lookup route behind authorizer' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: R1 + operation: add + mechanism: authenticated_api_exposure_no_lambda + path: Internet -> ApiGateway[GET /lookup, TokenAuthorizer] -> DynamoDB(TicketStore) + controls_present: + - TokenAuthorizer + - IAM(scoped read role) + controls_absent: + - WAF + component: TicketStore + transitive_reach: + - TicketStore(read) + construct_ids: + - SupportApi + - TicketLookupApiRole + - TicketStore + template_assertion: a Method with Integration Type AWS (not AWS_PROXY) targeting dynamodb:GetItem + appears + note: "No Lambda on the path \u2014 a Lambda-integration walk sees nothing. Auth holds, so\ + \ Mild." + C7: + sha: 1bf2978 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C8: + sha: e644ef2 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: benign config + C9: + sha: db752ea + label: VB + message: 'docs: document lookup route contract' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no template change + C10: + sha: 4e90f88 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: zero template diff + C11: + sha: d3da43b + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: docs-only + C12: + sha: da7a3a7 + label: V3 + message: 'fix: make lookup route public for the status page' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: R1 + operation: mutate + mechanism: auth_boundary_removal + path: Internet(anonymous) -> ApiGateway[GET /lookup] -> DynamoDB(TicketStore) + controls_present: + - IAM(scoped read role) + controls_removed: + - TokenAuthorizer + component: TicketStore + transitive_reach: + - TicketStore(read) by anyone + construct_ids: + - SupportApi + - TicketStore + template_assertion: the /lookup Method loses AuthorizerId; the AWS integration is unchanged + note: Anonymous read of all ticket data. The surviving IAM role is what lets API GW reach the + table, not a caller control. + C13: + sha: f428780 + label: VB + message: 'docs: mark lookup as public' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 on 80/443 at C1 +- defaultCorsPreflightOptions uses Cors.ALL_ORIGINS +- existing /tickets and /tickets-async routes carry their own baseline posture +scoring: + drift_commits: + - C6 + - C12 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C7 + - C8 + - C9 + - C10 + - C11 + - C13 + false_positive_probes: + - C7 + false_negative_probes: + - C6 + - C12 + state_vs_event_probes: + - C7 + - C8 + - C9 + - C10 + - C11 + - C13 + severity_axis: TokenAuthorizer presence on a no-Lambda direct-integration route + note: 'False-negative probe: no Lambda in the path; pairs with ECS R5 and paste R5.' diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/repository new file mode 160000 index 00000000..f428780a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/repository @@ -0,0 +1 @@ +Subproject commit f428780ae0a4c96425ff3029c828f4c73a2124f2 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/template_analysis.json new file mode 100644 index 00000000..a88d2645 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/R5/template_analysis.json @@ -0,0 +1,686 @@ +{ + "benchmark": "R5", + "drift_name": "API Gateway -> DynamoDB Direct Integration", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "de51dbe", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 1, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "e26c8cc", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "056cfa2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "6ab6014", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 94, + "template_diff_lines": 195, + "api_methods": [ + { + "logical_id": "SupportApilookupGETD0E057BF", + "http_method": "GET", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "1bf2978", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 94, + "template_diff_lines": 793, + "api_methods": [ + { + "logical_id": "SupportApilookupGETD0E057BF", + "http_method": "GET", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "e644ef2", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 95, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApilookupGETD0E057BF", + "http_method": "GET", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "db752ea", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 95, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApilookupGETD0E057BF", + "http_method": "GET", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "4e90f88", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 95, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApilookupGETD0E057BF", + "http_method": "GET", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C11": { + "sha": "d3da43b", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 95, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApilookupGETD0E057BF", + "http_method": "GET", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C12": { + "sha": "da7a3a7", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 95, + "template_diff_lines": 9, + "api_methods": [ + { + "logical_id": "SupportApilookupGETD0E057BF", + "http_method": "GET", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C13": { + "sha": "f428780", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 95, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApilookupGETD0E057BF", + "http_method": "GET", + "authorization_type": "NONE", + "has_authorizer": false, + "integration_type": "AWS" + }, + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/README.md new file mode 100644 index 00000000..b29e0aed --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/README.md @@ -0,0 +1,132 @@ +# Drift Benchmark Suite — `support-ticketing-system` + +Third suite in the family, alongside [`benchmarks/`](../benchmarks) (text-paste, 25) and +[`benchmarks-ecs/`](../benchmarks-ecs) (containerized-microservices, 30). **28 +independent parallel evolutions** of a hybrid support-ticketing platform — the first +baseline with **Lambda + ECS Fargate + EC2 Auto Scaling all in one stack**, plus a custom +API Gateway `TokenAuthorizer`, DynamoDB, RDS, SQS, SNS, EventBridge, KMS and SSM. + +**241 commits · 241 CloudFormation templates · 75 MB · shared C1 = `99c3992`.** + +## Families + +| Family | Benchmarks | Axis | +|--------|-----------|------| +| **R** — Architectural Reachability | R1–R5 | internal compute becomes reachable, then loses auth | +| **G** — Privilege | G1–G7 | IAM breadth (actions × resources) | +| **E** — Exposure | E1–E5 | existing surface loses restrictions | +| **D** — Dependency / Integration | D1–D4 | third-party enters critical path | +| **T** — Trust Boundary | T1–T7 | privilege crosses account/environment | + +Each benchmark labels **exactly two commits** V2 (Mild) and V3 (Severe); the rest are +benign. C1 is byte-identical across all 28 (same content, author, date → same SHA). + +## Commit-count distribution + +Non-linear by design; counts span **4 to 15** with no drift-position collision. + +| Count | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | +|---|---|---|---|---|---|---|---|---|---|---|---|---| +| Benchmarks | E1 G4 | D4 E5 G2 | R3 D2 E4 G6 T4 | E2 T7 | R4 T1 | R1 G1 G5 T2 | D1 T5 | R2 T3 | T6 | D3 E3 R5 | G3 | G7 | + +## Anti-correlation property + +**In all 28 benchmarks the Severe commit produces a smaller template diff than at least +one benign commit** — usually 100–400×. Cost-allocation tag propagation is the biggest +benign diff; severe drifts flip one or two fields. + +| Benchmark | Severe diff | Biggest benign | Ratio | +|---|---|---|---| +| G4 (PowerUser→Admin) | **2 lines** | 84 | 42:1 | +| T2 (cross-account role→Admin) | **2 lines** | 791 | **396:1** | +| D3 (`STRIPE_VERIFY_SIGNATURE` flip) | **2 lines** | 823 | **412:1** | +| R3 (admin port → 0.0.0.0/0) | 4 lines | 777 | 194:1 | +| E1 (S3 BlockPublicAccess off) | 4 lines | 84 | 21:1 | + +A detector triaging by diff magnitude scores worse than chance. + +## New drift axes over the two prior suites + +Eight axes unlocked by this baseline's service mix: + +| ID | Axis | +|---|---| +| R1, R4, R5 | custom `TokenAuthorizer` reachability, EC2-ASG admin port, APIGW→DynamoDB no-Lambda route | +| G6 | DynamoDB action wildcard (`dynamodb:*`) | +| G7, T4 | SSM parameter access — local wildcard and cross-account | +| E4 | S3 bucket-policy exposure (distinct from BlockPublicAccess) | +| E5 | SNS topic policy exposure (Subscribe + Publish) | +| T7 | SQS cross-account SendMessage (queue-injection) | + +## Cross-suite scoring pairs + +| Pair | Both end with | Discriminator | +|---|---|---| +| paste G4 ↔ ecs G4 ↔ **support G4 ↔ support T2 ↔ support D4** | `AdministratorAccess` | internal / cross-account partner / cross-account vendor | +| paste R1 ↔ **support R1** | API-fronted internal Lambda | Cognito vs custom `TokenAuthorizer` | +| paste E5 ↔ ecs E3 ↔ **support E1** | S3 BlockPublicAccess off | triple-baseline oracle mechanism | +| paste D3 ↔ ecs D2 ↔ **support D3** | webhook signature verification flipped | `AuthorizationType: NONE` correct at both drift commits | +| ecs T5 ↔ **support T5**, ecs T8 ↔ **support T6** | wildcard event-bus / KMS grant | different baselines | + +## Per-benchmark layout + +Each `benchmarks-support//` mirrors the source dataset's `/cdk/` shape: + +``` +R1/ +├── repository/ real git repo + real .git +├── commits/ +│ ├── C1..CN/ +│ │ ├── cdk/ same layout as support-ticketing-system/cdk/ +│ │ │ ├── cdk.ts, cdk.json, package.json, tsconfig.json +│ │ │ ├── lambda/ the three real handlers (ticket-creator/updater, notification) +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json +│ │ └── diff.patch +│ └── provenance.json +├── metadata.yaml ground truth (generated from the module) +├── template_analysis.json measured diffs + control state +└── README.md generated per benchmark +``` + +## Generators + +``` +python3 generators/build.py --suite benchmarks-support --all \ + --baseline /support-ticketing-system/cdk --out-root benchmarks-support +python3 generators/synth.py --root benchmarks-support/R1 --workbench /tmp/wb \ + --context-json "$(cat benchmarks-support/synth_context.json)" +python3 generators/analyze.py --root benchmarks-support/R1 +python3 generators/docs.py --suite benchmarks-support --root-dir benchmarks-support +python3 generators/verify.py --suite benchmarks-support --all --root-dir benchmarks-support +``` + +Workbench: `npm install aws-cdk-lib@2.150.0 constructs@^10 ts-node typescript@5.6.3 @types/node`. + +## Baseline handling (recorded caveats) + +Three engine accommodations, all deterministic and documented in +`generators/benchmarks-support/_support_common.py`: + +1. **No `package.json` / `tsconfig.json` in the baseline** — the module supplies defaults + via `BASELINE_DEFAULTS`. +2. **Two RDS props renamed for 2.150.0** — `encrypted` → `storageEncrypted`, + `encryptionKey` → `storageEncryptionKey` (`NORMALIZE_BASELINE_CDK_TS`). +3. **Context injection** — the baseline reads `app.node.tryGetContext()`; raw `ts-node` + doesn't load `cdk.json` context, so `synth.py` injects it via `CDK_CONTEXT_JSON` + (shipped as `synth_context.json`). Feature flags default `enableKmsEncryption=true` + and `enableSsoIntegration=true`, so the KMS key and TokenAuthorizer exist at C1. + +The shared C1 SHA `99c3992` is deterministic from the normalized source. + +## Validation performed + +- **Real git.** Genuine objects, refs, reflogs — `git log/diff/bisect` all work. +- **Deterministic.** Same baseline → same SHAs. +- **Typechecked.** `cdk.ts` compiles under `tsc --noEmit` at every commit. +- **Synthesized.** All 241 commits produce valid CloudFormation (241 templates; resource + counts 85 baseline → 101). +- **Verified.** `verify.py` asserts SHAs, messages and per-commit invariants — e.g. R1's + notification endpoint gains then loses its `TokenAuthorizer`; T2's `PartnerAccessRole` + managed policy flips `ReadOnlyAccess` → `AdministratorAccess` while its cross-account + trust is preserved. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/README.md new file mode 100644 index 00000000..b30e0a1f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/README.md @@ -0,0 +1,147 @@ +# T1 — Dev <-> Prod VPC Peering + +**Family:** Trust Boundary Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The support VPC is peered to prod with a route restricted to the prod admin subnet (C4, Mild). Three commits later the route widens to the whole prod CIDR and the SG opens to the peered range (C7, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Route destination CIDR and SG source range on the peering. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `1d927b7` | V2 | **Mild** | Mild | 93 | **59** | +| C5 | `90025f8` | VB | None | Mild | 93 | 805 | +| C6 | `ac73073` | VB | None | Mild | 93 | 2 | +| C7 | `2376964` | V3 | **Severe** | Severe | 93 | **20** | +| C8 | `f0004e9` | VB | None | Severe | 93 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 805 lines**. +The smallest drift-bearing diff is **C7 at 20 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: peer support vpc to prod admin subnet` + +- **Mechanism:** `cross_environment_peering_narrow` +- **Path:** `SupportVpc -> ProdPeering -> 10.30.10.0/24` +- **Controls present:** NarrowRouteDestination, SgIngressSingleHost +- **Component:** `ProdPeering` +- **Transitive reach:** one /24 in prod +- **Template assertion:** VPCPeeringConnection to vpc-prod00000000 + Route with /24 destination + +First route across the env boundary; narrow. Mild. + +### C7 — V3, Severe + +`fix: widen peering routes for the migration` + +- **Mechanism:** `cross_environment_route_widening` +- **Path:** `SupportVpc -> ProdPeering -> 10.30.0.0/16` +- **Controls present:** none +- **Controls removed:** NarrowRouteDestination, SgIngressSingleHost +- **Component:** `ProdPeering` +- **Transitive reach:** every private IP in prod, all ports +- **Template assertion:** Route destination /24 -> /16; SG ingress /32 -> /16 all-traffic + +Peering unchanged; route + SG widen it into an unrestricted lateral hop. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no trust change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: raise rds backup retention floor` — unrelated hardening +- **C8** `chore: extend gitignore` — zero template diff while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- prod VPC is external — its side not visible here + +## Scoring + +- **Drift commits:** C4, C7 +- **Benign commits:** C2, C3, C5, C6, C8 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C5, C6, C8 + +Cross-environment boundary the repo can't see the other side of. + +## Layout + +``` +T1/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark t1 --baseline /text-paste-service/v1/cdk --out T1 +python3 generators/synth.py --root T1 --workbench /tmp/wb +python3 generators/analyze.py --root T1 +python3 generators/verify.py --benchmark t1 --root T1 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/t1.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, C7, C8 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..20ed9594 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/cdk.ts @@ -0,0 +1,453 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-environment peering to prod (narrow). + const prodPeering = new ec2.CfnVPCPeeringConnection(this, 'ProdPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-prod00000000', + peerOwnerId: '222233334444', + }); + vpc.privateSubnets.forEach((subnet, idx) => { + new ec2.CfnRoute(this, `ToProdAdmin${idx}`, { + routeTableId: subnet.routeTable.routeTableId, + destinationCidrBlock: '10.30.10.0/24', + vpcPeeringConnectionId: prodPeering.ref, + }); + }); + const peeringSg = new ec2.SecurityGroup(this, 'PeeringSg', { vpc, allowAllOutbound: true }); + peeringSg.addIngressRule(ec2.Peer.ipv4('10.30.10.5/32'), ec2.Port.tcp(22), 'Prod bastion'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/commit.json new file mode 100644 index 00000000..aef2b301 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "1d927b7", + "full_sha": "1d927b74703add12892faedabc7e2bf0fb8efd79", + "parent": "55a045d", + "message": "feat: peer support vpc to prod admin subnet", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 16 ++++++++++++++++\n 1 file changed, 16 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/diff.patch new file mode 100644 index 00000000..057e94ad --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C4/diff.patch @@ -0,0 +1,27 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..20ed959 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -381,6 +381,22 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Cross-environment peering to prod (narrow). ++ const prodPeering = new ec2.CfnVPCPeeringConnection(this, 'ProdPeering', { ++ vpcId: vpc.vpcId, ++ peerVpcId: 'vpc-prod00000000', ++ peerOwnerId: '222233334444', ++ }); ++ vpc.privateSubnets.forEach((subnet, idx) => { ++ new ec2.CfnRoute(this, `ToProdAdmin${idx}`, { ++ routeTableId: subnet.routeTable.routeTableId, ++ destinationCidrBlock: '10.30.10.0/24', ++ vpcPeeringConnectionId: prodPeering.ref, ++ }); ++ }); ++ const peeringSg = new ec2.SecurityGroup(this, 'PeeringSg', { vpc, allowAllOutbound: true }); ++ peeringSg.addIngressRule(ec2.Peer.ipv4('10.30.10.5/32'), ec2.Port.tcp(22), 'Prod bastion'); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..01cba670 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/cdk.ts @@ -0,0 +1,458 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-environment peering to prod (narrow). + const prodPeering = new ec2.CfnVPCPeeringConnection(this, 'ProdPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-prod00000000', + peerOwnerId: '222233334444', + }); + vpc.privateSubnets.forEach((subnet, idx) => { + new ec2.CfnRoute(this, `ToProdAdmin${idx}`, { + routeTableId: subnet.routeTable.routeTableId, + destinationCidrBlock: '10.30.10.0/24', + vpcPeeringConnectionId: prodPeering.ref, + }); + }); + const peeringSg = new ec2.SecurityGroup(this, 'PeeringSg', { vpc, allowAllOutbound: true }); + peeringSg.addIngressRule(ec2.Peer.ipv4('10.30.10.5/32'), ec2.Port.tcp(22), 'Prod bastion'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/commit.json new file mode 100644 index 00000000..6665ffa2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "90025f8", + "full_sha": "90025f845a631fd708e5339f129ac9b095386525", + "parent": "1d927b7", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/diff.patch new file mode 100644 index 00000000..8dc8576c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 20ed959..01cba67 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..a0c5553a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/cdk.ts @@ -0,0 +1,458 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-environment peering to prod (narrow). + const prodPeering = new ec2.CfnVPCPeeringConnection(this, 'ProdPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-prod00000000', + peerOwnerId: '222233334444', + }); + vpc.privateSubnets.forEach((subnet, idx) => { + new ec2.CfnRoute(this, `ToProdAdmin${idx}`, { + routeTableId: subnet.routeTable.routeTableId, + destinationCidrBlock: '10.30.10.0/24', + vpcPeeringConnectionId: prodPeering.ref, + }); + }); + const peeringSg = new ec2.SecurityGroup(this, 'PeeringSg', { vpc, allowAllOutbound: true }); + peeringSg.addIngressRule(ec2.Peer.ipv4('10.30.10.5/32'), ec2.Port.tcp(22), 'Prod bastion'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/commit.json new file mode 100644 index 00000000..7f386615 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "ac73073", + "full_sha": "ac7307399e25bc02825295c34d6549ef588c8f5f", + "parent": "90025f8", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/diff.patch new file mode 100644 index 00000000..9c9ece89 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 01cba67..a0c5553 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..e2b33525 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/cdk.ts @@ -0,0 +1,458 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-environment peering to prod (wide). + const prodPeering = new ec2.CfnVPCPeeringConnection(this, 'ProdPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-prod00000000', + peerOwnerId: '222233334444', + }); + vpc.privateSubnets.forEach((subnet, idx) => { + new ec2.CfnRoute(this, `ToProdAll${idx}`, { + routeTableId: subnet.routeTable.routeTableId, + destinationCidrBlock: '10.30.0.0/16', + vpcPeeringConnectionId: prodPeering.ref, + }); + }); + const peeringSg = new ec2.SecurityGroup(this, 'PeeringSg', { vpc, allowAllOutbound: true }); + peeringSg.addIngressRule(ec2.Peer.ipv4('10.30.0.0/16'), ec2.Port.allTraffic(), 'Peered from prod'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/commit.json new file mode 100644 index 00000000..3a24cd21 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "2376964", + "full_sha": "2376964ffd7237bbe4f960611dd30606aa2e2aeb", + "parent": "ac73073", + "message": "fix: widen peering routes for the migration", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 8 ++++----\n 1 file changed, 4 insertions(+), 4 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/diff.patch new file mode 100644 index 00000000..c0160c28 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C7/diff.patch @@ -0,0 +1,30 @@ +diff --git a/cdk.ts b/cdk.ts +index a0c5553..e2b3352 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -386,21 +386,21 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + +- // Cross-environment peering to prod (narrow). ++ // Cross-environment peering to prod (wide). + const prodPeering = new ec2.CfnVPCPeeringConnection(this, 'ProdPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-prod00000000', + peerOwnerId: '222233334444', + }); + vpc.privateSubnets.forEach((subnet, idx) => { +- new ec2.CfnRoute(this, `ToProdAdmin${idx}`, { ++ new ec2.CfnRoute(this, `ToProdAll${idx}`, { + routeTableId: subnet.routeTable.routeTableId, +- destinationCidrBlock: '10.30.10.0/24', ++ destinationCidrBlock: '10.30.0.0/16', + vpcPeeringConnectionId: prodPeering.ref, + }); + }); + const peeringSg = new ec2.SecurityGroup(this, 'PeeringSg', { vpc, allowAllOutbound: true }); +- peeringSg.addIngressRule(ec2.Peer.ipv4('10.30.10.5/32'), ec2.Port.tcp(22), 'Prod bastion'); ++ peeringSg.addIngressRule(ec2.Peer.ipv4('10.30.0.0/16'), ec2.Port.allTraffic(), 'Peered from prod'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..e2b33525 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/cdk.ts @@ -0,0 +1,458 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-environment peering to prod (wide). + const prodPeering = new ec2.CfnVPCPeeringConnection(this, 'ProdPeering', { + vpcId: vpc.vpcId, + peerVpcId: 'vpc-prod00000000', + peerOwnerId: '222233334444', + }); + vpc.privateSubnets.forEach((subnet, idx) => { + new ec2.CfnRoute(this, `ToProdAll${idx}`, { + routeTableId: subnet.routeTable.routeTableId, + destinationCidrBlock: '10.30.0.0/16', + vpcPeeringConnectionId: prodPeering.ref, + }); + }); + const peeringSg = new ec2.SecurityGroup(this, 'PeeringSg', { vpc, allowAllOutbound: true }); + peeringSg.addIngressRule(ec2.Peer.ipv4('10.30.0.0/16'), ec2.Port.allTraffic(), 'Peered from prod'); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/commit.json new file mode 100644 index 00000000..2f03d94e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "f0004e9", + "full_sha": "f0004e936f96417859c4d8336f885c1183f5fcea", + "parent": "2376964", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/C8/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/provenance.json new file mode 100644 index 00000000..58145f23 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/commits/provenance.json @@ -0,0 +1,122 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "1d927b7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "90025f8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "ac73073", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "2376964", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "f0004e9", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/metadata.yaml new file mode 100644 index 00000000..c7757ecb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/metadata.yaml @@ -0,0 +1,136 @@ +benchmark: T1 +family: Trust Boundary Drift +drift_name: Dev <-> Prod VPC Peering +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The support VPC is peered to prod with a route restricted to the prod admin subnet (C4, + Mild). Three commits later the route widens to the whole prod CIDR and the SG opens to the peered + range (C7, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: Route destination CIDR and SG source range on the peering. +commit_count: 8 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no trust change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: 1d927b7 + label: V2 + message: 'feat: peer support vpc to prod admin subnet' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: cross_environment_peering_narrow + path: SupportVpc -> ProdPeering -> 10.30.10.0/24 + controls_present: + - NarrowRouteDestination + - SgIngressSingleHost + controls_absent: + - ProdSideRoutePolicy(external) + component: ProdPeering + transitive_reach: + - one /24 in prod + construct_ids: + - ProdPeering + template_assertion: VPCPeeringConnection to vpc-prod00000000 + Route with /24 destination + note: First route across the env boundary; narrow. Mild. + C5: + sha: 90025f8 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: ac73073 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C7: + sha: '2376964' + label: V3 + message: 'fix: widen peering routes for the migration' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: cross_environment_route_widening + path: SupportVpc -> ProdPeering -> 10.30.0.0/16 + controls_present: [] + controls_removed: + - NarrowRouteDestination + - SgIngressSingleHost + component: ProdPeering + transitive_reach: + - every private IP in prod, all ports + construct_ids: + - ProdPeering + - PeeringSg + template_assertion: Route destination /24 -> /16; SG ingress /32 -> /16 all-traffic + note: Peering unchanged; route + SG widen it into an unrestricted lateral hop. + C8: + sha: f0004e9 + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff while cumulative Severe +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- "prod VPC is external \u2014 its side not visible here" +scoring: + drift_commits: + - C4 + - C7 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C8 + false_positive_probes: + - C5 + state_vs_event_probes: + - C5 + - C6 + - C8 + severity_axis: route destination CIDR + SG source range on the peering + note: Cross-environment boundary the repo can't see the other side of. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/repository new file mode 160000 index 00000000..f0004e93 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/repository @@ -0,0 +1 @@ +Subproject commit f0004e936f96417859c4d8336f885c1183f5fcea diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/template_analysis.json new file mode 100644 index 00000000..f6c643da --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T1/template_analysis.json @@ -0,0 +1,390 @@ +{ + "benchmark": "T1", + "drift_name": "Dev <-> Prod VPC Peering", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "1d927b7", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 93, + "template_diff_lines": 59, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "90025f8", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 93, + "template_diff_lines": 805, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "ac73073", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 93, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "2376964", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 93, + "template_diff_lines": 20, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "f0004e9", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 93, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/README.md new file mode 100644 index 00000000..54bb3991 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/README.md @@ -0,0 +1,150 @@ +# T2 — Cross-Account IAM + +**Family:** Trust Boundary Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A role trusts external account 333344445555 with ReadOnlyAccess (C4, Mild); the managed policy is swapped for AdministratorAccess with the trust unchanged (C7, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Managed policy on a role whose trust crosses the account boundary. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `509ff04` | V2 | **Mild** | Mild | 90 | **43** | +| C5 | `541d3fb` | VB | None | Mild | 90 | 791 | +| C6 | `8a509db` | VB | None | Mild | 90 | 2 | +| C7 | `ee65b2a` | V3 | **Severe** | Severe | 90 | **2** | +| C8 | `6b1f393` | VB | None | Severe | 90 | **0** | +| C9 | `861330c` | VB | None | Severe | 90 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C5 at 791 lines**. +The smallest drift-bearing diff is **C7 at 2 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C4 — V2, Mild + +`feat: cross-account read role for analytics partner` + +- **Mechanism:** `cross_account_trust_read` +- **Path:** `AWS 333344445555 -> sts:AssumeRole -> PartnerAccessRole -> ReadOnlyAccess` +- **Controls present:** ReadOnlyManagedPolicy, SingleNamedExternalAccount +- **Component:** `PartnerAccessRole` +- **Transitive reach:** read of everything by a foreign principal +- **Template assertion:** IAM Role trusting AWS 333344445555 with ReadOnlyAccess + +First trust across the account boundary; read-only. Mild. + +### C7 — V3, Severe + +`fix: escalate partner role to administrator access` + +- **Mechanism:** `cross_account_privilege_escalation` +- **Path:** `AWS 333344445555 -> sts:AssumeRole -> PartnerAccessRole -> AdministratorAccess` +- **Controls present:** none +- **Controls removed:** ReadOnlyManagedPolicy +- **Component:** `PartnerAccessRole` +- **Transitive reach:** full account control by a foreign principal +- **Template assertion:** ManagedPolicyArns ReadOnlyAccess -> AdministratorAccess; trust unchanged + +Two ARN characters. Worse than G4 because trust already crosses accounts. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no trust change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C5** `chore: apply cost-allocation tags` — largest template diff +- **C6** `chore: raise rds backup retention floor` — unrelated hardening +- **C8** `docs: add ops runbook` — no new drift while cumulative Severe +- **C9** `chore: extend gitignore` — zero template diff + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- task roles hold broad grants + +## Scoring + +- **Drift commits:** C4, C7 +- **Benign commits:** C2, C3, C5, C6, C8, C9 +- **False-positive probes:** C5 + +- **State-vs-event probes:** C5, C6, C8, C9 + +Cross-family/suite pair: G4 (internal), D4 (vendor), paste/ECS G4. + +## Layout + +``` +T2/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark t2 --baseline /text-paste-service/v1/cdk --out T2 +python3 generators/synth.py --root T2 --workbench /tmp/wb +python3 generators/analyze.py --root T2 +python3 generators/verify.py --benchmark t2 --root T2 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/t2.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..f6de20bf --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + const partnerRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('333344445555'), + description: 'Partner cross-account access', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/commit.json new file mode 100644 index 00000000..d1adfe07 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "509ff04", + "full_sha": "509ff0404d4599e2d0ace061dfe6d0d8c3b3ae9d", + "parent": "55a045d", + "message": "feat: cross-account read role for analytics partner", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/diff.patch new file mode 100644 index 00000000..da3c3c40 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C4/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..f6de20b 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -381,6 +381,12 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ const partnerRole = new iam.Role(this, 'PartnerAccessRole', { ++ assumedBy: new iam.AccountPrincipal('333344445555'), ++ description: 'Partner cross-account access', ++ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], ++ }); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..a6b2ec11 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/cdk.ts @@ -0,0 +1,448 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + const partnerRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('333344445555'), + description: 'Partner cross-account access', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/commit.json new file mode 100644 index 00000000..effeae77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "541d3fb", + "full_sha": "541d3fbbb23cace4655b7fe21b991be0ad38befa", + "parent": "509ff04", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/diff.patch new file mode 100644 index 00000000..3651883f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C5/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index f6de20b..a6b2ec1 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..fa0fe11c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/cdk.ts @@ -0,0 +1,448 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + const partnerRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('333344445555'), + description: 'Partner cross-account access', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/commit.json new file mode 100644 index 00000000..88963ddb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "8a509db", + "full_sha": "8a509dbdbe306d5485a714c232d0f823ec1273d6", + "parent": "541d3fb", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/diff.patch new file mode 100644 index 00000000..0d11a442 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index a6b2ec1..fa0fe11 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..25d7fa77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/cdk.ts @@ -0,0 +1,449 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Partner needs to reconfigure their delivery stack in our account. Escalating. + const partnerRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('333344445555'), + description: 'Partner cross-account access', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/commit.json new file mode 100644 index 00000000..5eacdffb --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "ee65b2a", + "full_sha": "ee65b2ab1880b296af1b72a5d77309d6c9e9d634", + "parent": "8a509db", + "message": "fix: escalate partner role to administrator access", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 3 ++-\n 1 file changed, 2 insertions(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/diff.patch new file mode 100644 index 00000000..a4ec042c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C7/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index fa0fe11..25d7fa7 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -386,10 +386,11 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Partner needs to reconfigure their delivery stack in our account. Escalating. + const partnerRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('333344445555'), + description: 'Partner cross-account access', +- managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('ReadOnlyAccess')], ++ managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // Outputs \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..25d7fa77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/cdk.ts @@ -0,0 +1,449 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Partner needs to reconfigure their delivery stack in our account. Escalating. + const partnerRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('333344445555'), + description: 'Partner cross-account access', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/commit.json new file mode 100644 index 00000000..2b8b1817 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "6b1f393", + "full_sha": "6b1f393b5e3270da8f46e2805c933464e270b88d", + "parent": "ee65b2a", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C8/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..25d7fa77 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/cdk.ts @@ -0,0 +1,449 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Partner needs to reconfigure their delivery stack in our account. Escalating. + const partnerRole = new iam.Role(this, 'PartnerAccessRole', { + assumedBy: new iam.AccountPrincipal('333344445555'), + description: 'Partner cross-account access', + managedPolicies: [iam.ManagedPolicy.fromAwsManagedPolicyName('AdministratorAccess')], + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/commit.json new file mode 100644 index 00000000..6ea48127 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "861330c", + "full_sha": "861330c4383175d4a61b4195cfae0aca33575077", + "parent": "6b1f393", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/C9/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/provenance.json new file mode 100644 index 00000000..69d02ecd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/commits/provenance.json @@ -0,0 +1,137 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "509ff04", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "541d3fb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "8a509db", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "ee65b2a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "6b1f393", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "861330c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/metadata.yaml new file mode 100644 index 00000000..2d4f4cd9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/metadata.yaml @@ -0,0 +1,144 @@ +benchmark: T2 +family: Trust Boundary Drift +drift_name: Cross-Account IAM +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A role trusts external account 333344445555 with ReadOnlyAccess (C4, Mild); the managed + policy is swapped for AdministratorAccess with the trust unchanged (C7, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: Managed policy on a role whose trust crosses the account boundary. +commit_count: 9 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no trust change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: 509ff04 + label: V2 + message: 'feat: cross-account read role for analytics partner' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: cross_account_trust_read + path: AWS 333344445555 -> sts:AssumeRole -> PartnerAccessRole -> ReadOnlyAccess + controls_present: + - ReadOnlyManagedPolicy + - SingleNamedExternalAccount + controls_absent: + - ExternalId + - MFACondition + component: PartnerAccessRole + transitive_reach: + - read of everything by a foreign principal + construct_ids: + - PartnerAccessRole + template_assertion: IAM Role trusting AWS 333344445555 with ReadOnlyAccess + note: First trust across the account boundary; read-only. Mild. + C5: + sha: 541d3fb + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C6: + sha: 8a509db + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C7: + sha: ee65b2a + label: V3 + message: 'fix: escalate partner role to administrator access' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: cross_account_privilege_escalation + path: AWS 333344445555 -> sts:AssumeRole -> PartnerAccessRole -> AdministratorAccess + controls_present: [] + controls_removed: + - ReadOnlyManagedPolicy + component: PartnerAccessRole + transitive_reach: + - full account control by a foreign principal + construct_ids: + - PartnerAccessRole + template_assertion: ManagedPolicyArns ReadOnlyAccess -> AdministratorAccess; trust unchanged + note: Two ARN characters. Worse than G4 because trust already crosses accounts. + C8: + sha: 6b1f393 + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no new drift while cumulative Severe + C9: + sha: 861330c + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- task roles hold broad grants +scoring: + drift_commits: + - C4 + - C7 + benign_commits: + - C2 + - C3 + - C5 + - C6 + - C8 + - C9 + false_positive_probes: + - C5 + state_vs_event_probes: + - C5 + - C6 + - C8 + - C9 + severity_axis: managed policy on a cross-account-trusted role + note: 'Cross-family/suite pair: G4 (internal), D4 (vendor), paste/ECS G4.' diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/repository new file mode 160000 index 00000000..861330c4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/repository @@ -0,0 +1 @@ +Subproject commit 861330c4383175d4a61b4195cfae0aca33575077 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/template_analysis.json new file mode 100644 index 00000000..bbc9ea13 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T2/template_analysis.json @@ -0,0 +1,444 @@ +{ + "benchmark": "T2", + "drift_name": "Cross-Account IAM", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "509ff04", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 43, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "541d3fb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 791, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "8a509db", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "ReadOnlyAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "ee65b2a", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "6b1f393", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "861330c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "AdministratorAccess", + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/README.md new file mode 100644 index 00000000..57e916a2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/README.md @@ -0,0 +1,156 @@ +# T3 — Shared Database + +**Family:** Trust Boundary Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A cross-account read replica of the prod primary is attached (C5, Mild). Five commits later a lambda gains a cross-account secret granting write to the prod DB (C10, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Direction of data flow across the environment boundary. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `3ba74ec` | VB | None | None | 89 | 1 | +| C5 | `97d1f1e` | V2 | **Mild** | Mild | 92 | **63** | +| C6 | `fc9f3eb` | VB | None | Mild | 92 | 819 | +| C7 | `0d1f14c` | VB | None | Mild | 92 | 2 | +| C8 | `208cdf1` | VB | None | Mild | 93 | 14 | +| C9 | `24293d1` | VB | None | Mild | 93 | **0** | +| C10 | `d1a0b8a` | V3 | **Severe** | Severe | 93 | **8** | +| C11 | `bf45edd` | VB | None | Severe | 93 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C6 at 819 lines**. +The smallest drift-bearing diff is **C10 at 8 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: add read replica of prod database for analytics` + +- **Mechanism:** `cross_env_read_replica` +- **Path:** `ProdPrimary(222233334444) -> ProdReadReplica(this)` +- **Controls present:** ReadDirectionOnly +- **Component:** `ProdReadReplica` +- **Transitive reach:** prod rows readable in-account +- **Template assertion:** RDS DBInstance with SourceDBInstanceIdentifier support-prod-primary + +Data crosses one-way. Mild. + +### C10 — V3, Severe + +`feat: allow creator lambda to backfill prod entries` + +- **Mechanism:** `cross_env_write_via_shared_secret` +- **Path:** `LambdaExecutionRole -> ProdDbCredentials(cross-account) -> ProdPrimary` +- **Controls present:** none +- **Controls removed:** ReadDirectionOnly +- **Component:** `lambda functions, ProdPrimary(external)` +- **Transitive reach:** write to prod primary from this account +- **Template assertion:** lambda role gains GetSecretValue on an ARN in account 222233334444 + +Direction inverts: this account can now mutate prod data. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no data-flow change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C4** `chore: add log level env to creator lambda` — small lambda diff +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: raise rds backup retention floor` — unrelated hardening +- **C8** `chore: add api stage ssm parameter` — benign config +- **C9** `docs: document replica lag expectations` — no template change +- **C11** `docs: on-call contact note` — docs-only while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- RDS RemovalPolicy.DESTROY at C1 +- frontendSg 0.0.0.0/0 at C1 + +## Scoring + +- **Drift commits:** C5, C10 +- **Benign commits:** C2, C3, C4, C6, C7, C8, C9, C11 +- **False-positive probes:** C6 + +- **State-vs-event probes:** C6, C7, C8, C9, C11 + +Pairs with ECS T3; severity axis is direction, not scope. + +## Layout + +``` +T3/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark t3 --baseline /text-paste-service/v1/cdk --out T3 +python3 generators/synth.py --root T3 --workbench /tmp/wb +python3 generators/analyze.py --root T3 +python3 generators/verify.py --benchmark t3 --root T3 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/t3.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C11, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..ce6b119a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/cdk.ts @@ -0,0 +1,469 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // Read replica of the prod-owned primary (one-way). + const prodReplica = new rds.DatabaseInstanceReadReplica(this, 'ProdReadReplica', { + sourceDatabaseInstance: rds.DatabaseInstance.fromDatabaseInstanceAttributes( + this, 'ProdPrimaryRef', { + instanceEndpointAddress: 'prod-db.222233334444.rds.internal', + instanceIdentifier: 'support-prod-primary', + port: 5432, + securityGroups: [rdsSg], + }), + instanceType: new ec2.InstanceType('t3.medium'), + vpc, + }); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-account write credential to the prod primary. + const prodDbSecret = secretsmanager.Secret.fromSecretCompleteArn( + this, 'ProdDbCredentials', + 'arn:aws:secretsmanager:us-east-1:222233334444:secret:support/prod-db-writer-XXXXXX'); + prodDbSecret.grantRead(lambdaExecutionRole); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/commit.json new file mode 100644 index 00000000..b0eb7377 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "d1a0b8a", + "full_sha": "d1a0b8ac4697ffafd40707ed67930dea0ef66622", + "parent": "24293d1", + "message": "feat: allow creator lambda to backfill prod entries", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/diff.patch new file mode 100644 index 00000000..17609c4e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C10/diff.patch @@ -0,0 +1,23 @@ +diff --git a/cdk.ts b/cdk.ts +index b9b231c..ce6b119 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -1,4 +1,5 @@ + import * as cdk from 'aws-cdk-lib'; ++import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; + import { Construct } from 'constructs'; + import * as ec2 from 'aws-cdk-lib/aws-ec2'; + import * as ecs from 'aws-cdk-lib/aws-ecs'; +@@ -406,6 +407,12 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Cross-account write credential to the prod primary. ++ const prodDbSecret = secretsmanager.Secret.fromSecretCompleteArn( ++ this, 'ProdDbCredentials', ++ 'arn:aws:secretsmanager:us-east-1:222233334444:secret:support/prod-db-writer-XXXXXX'); ++ prodDbSecret.grantRead(lambdaExecutionRole); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..ce6b119a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/cdk.ts @@ -0,0 +1,469 @@ +import * as cdk from 'aws-cdk-lib'; +import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // Read replica of the prod-owned primary (one-way). + const prodReplica = new rds.DatabaseInstanceReadReplica(this, 'ProdReadReplica', { + sourceDatabaseInstance: rds.DatabaseInstance.fromDatabaseInstanceAttributes( + this, 'ProdPrimaryRef', { + instanceEndpointAddress: 'prod-db.222233334444.rds.internal', + instanceIdentifier: 'support-prod-primary', + port: 5432, + securityGroups: [rdsSg], + }), + instanceType: new ec2.InstanceType('t3.medium'), + vpc, + }); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-account write credential to the prod primary. + const prodDbSecret = secretsmanager.Secret.fromSecretCompleteArn( + this, 'ProdDbCredentials', + 'arn:aws:secretsmanager:us-east-1:222233334444:secret:support/prod-db-writer-XXXXXX'); + prodDbSecret.grantRead(lambdaExecutionRole); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/commit.json new file mode 100644 index 00000000..2238583c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "bf45edd", + "full_sha": "bf45edd26833a7cca72a0f5871421f8edee1ba29", + "parent": "d1a0b8a", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C11/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..b499c6a3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/commit.json new file mode 100644 index 00000000..70b4d26c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "3ba74ec", + "full_sha": "3ba74ec6560256a9fba868fcd9702f6b2a33a0f2", + "parent": "55a045d", + "message": "chore: add log level env to creator lambda", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/diff.patch new file mode 100644 index 00000000..e2baa4c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C4/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..b499c6a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -174,6 +174,7 @@ export class SupportTicketingStack extends cdk.Stack { + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, ++ LOG_LEVEL: 'info', + }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..735793f4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/cdk.ts @@ -0,0 +1,451 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // Read replica of the prod-owned primary (one-way). + const prodReplica = new rds.DatabaseInstanceReadReplica(this, 'ProdReadReplica', { + sourceDatabaseInstance: rds.DatabaseInstance.fromDatabaseInstanceAttributes( + this, 'ProdPrimaryRef', { + instanceEndpointAddress: 'prod-db.222233334444.rds.internal', + instanceIdentifier: 'support-prod-primary', + port: 5432, + securityGroups: [rdsSg], + }), + instanceType: new ec2.InstanceType('t3.medium'), + vpc, + }); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/commit.json new file mode 100644 index 00000000..d90755bc --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "97d1f1e", + "full_sha": "97d1f1eaed3a11cbc1a24fb41c2853db530a88ea", + "parent": "3ba74ec", + "message": "feat: add read replica of prod database for analytics", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 13 +++++++++++++\n 1 file changed, 13 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/diff.patch new file mode 100644 index 00000000..a76653bc --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C5/diff.patch @@ -0,0 +1,24 @@ +diff --git a/cdk.ts b/cdk.ts +index b499c6a..735793f 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -227,6 +227,19 @@ export class SupportTicketingStack extends cdk.Stack { + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + ++ // Read replica of the prod-owned primary (one-way). ++ const prodReplica = new rds.DatabaseInstanceReadReplica(this, 'ProdReadReplica', { ++ sourceDatabaseInstance: rds.DatabaseInstance.fromDatabaseInstanceAttributes( ++ this, 'ProdPrimaryRef', { ++ instanceEndpointAddress: 'prod-db.222233334444.rds.internal', ++ instanceIdentifier: 'support-prod-primary', ++ port: 5432, ++ securityGroups: [rdsSg], ++ }), ++ instanceType: new ec2.InstanceType('t3.medium'), ++ vpc, ++ }); ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..6fd5bdec --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // Read replica of the prod-owned primary (one-way). + const prodReplica = new rds.DatabaseInstanceReadReplica(this, 'ProdReadReplica', { + sourceDatabaseInstance: rds.DatabaseInstance.fromDatabaseInstanceAttributes( + this, 'ProdPrimaryRef', { + instanceEndpointAddress: 'prod-db.222233334444.rds.internal', + instanceIdentifier: 'support-prod-primary', + port: 5432, + securityGroups: [rdsSg], + }), + instanceType: new ec2.InstanceType('t3.medium'), + vpc, + }); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/commit.json new file mode 100644 index 00000000..efbd05de --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "fc9f3eb", + "full_sha": "fc9f3ebb1fe5a1dba07a038cf8b550aa858899dd", + "parent": "97d1f1e", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/diff.patch new file mode 100644 index 00000000..7b73c09e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 735793f..6fd5bde 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..1676a504 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/cdk.ts @@ -0,0 +1,456 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // Read replica of the prod-owned primary (one-way). + const prodReplica = new rds.DatabaseInstanceReadReplica(this, 'ProdReadReplica', { + sourceDatabaseInstance: rds.DatabaseInstance.fromDatabaseInstanceAttributes( + this, 'ProdPrimaryRef', { + instanceEndpointAddress: 'prod-db.222233334444.rds.internal', + instanceIdentifier: 'support-prod-primary', + port: 5432, + securityGroups: [rdsSg], + }), + instanceType: new ec2.InstanceType('t3.medium'), + vpc, + }); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/commit.json new file mode 100644 index 00000000..a6a5937e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "0d1f14c", + "full_sha": "0d1f14c3547d84c817b0850603281cd4e292fcaf", + "parent": "fc9f3eb", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/diff.patch new file mode 100644 index 00000000..f4808e84 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 6fd5bde..1676a50 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..b9b231ca --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/cdk.ts @@ -0,0 +1,462 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // Read replica of the prod-owned primary (one-way). + const prodReplica = new rds.DatabaseInstanceReadReplica(this, 'ProdReadReplica', { + sourceDatabaseInstance: rds.DatabaseInstance.fromDatabaseInstanceAttributes( + this, 'ProdPrimaryRef', { + instanceEndpointAddress: 'prod-db.222233334444.rds.internal', + instanceIdentifier: 'support-prod-primary', + port: 5432, + securityGroups: [rdsSg], + }), + instanceType: new ec2.InstanceType('t3.medium'), + vpc, + }); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/commit.json new file mode 100644 index 00000000..efc23e52 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "208cdf1", + "full_sha": "208cdf156f23732cd23805cc7192c2eb860ba255", + "parent": "0d1f14c", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/diff.patch new file mode 100644 index 00000000..0d642514 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C8/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 1676a50..b9b231c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -389,6 +389,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..b9b231ca --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/cdk.ts @@ -0,0 +1,462 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // Read replica of the prod-owned primary (one-way). + const prodReplica = new rds.DatabaseInstanceReadReplica(this, 'ProdReadReplica', { + sourceDatabaseInstance: rds.DatabaseInstance.fromDatabaseInstanceAttributes( + this, 'ProdPrimaryRef', { + instanceEndpointAddress: 'prod-db.222233334444.rds.internal', + instanceIdentifier: 'support-prod-primary', + port: 5432, + securityGroups: [rdsSg], + }), + instanceType: new ec2.InstanceType('t3.medium'), + vpc, + }); + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/commit.json new file mode 100644 index 00000000..3c5ed2c6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "24293d1", + "full_sha": "24293d1c5c33984d3dd5368b6478c3d9ffb388e1", + "parent": "208cdf1", + "message": "docs: document replica lag expectations", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/provenance.json new file mode 100644 index 00000000..f80a93d8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/commits/provenance.json @@ -0,0 +1,167 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "d1a0b8a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "bf45edd", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "3ba74ec", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "97d1f1e", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "fc9f3eb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 92, + "sha": "0d1f14c", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "208cdf1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 93, + "sha": "24293d1", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/metadata.yaml new file mode 100644 index 00000000..9506494a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/metadata.yaml @@ -0,0 +1,161 @@ +benchmark: T3 +family: Trust Boundary Drift +drift_name: Shared Database +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A cross-account read replica of the prod primary is attached (C5, Mild). Five commits + later a lambda gains a cross-account secret granting write to the prod DB (C10, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: Direction of data flow across the environment boundary. +commit_count: 11 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no data-flow change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: 3ba74ec + label: VB + message: 'chore: add log level env to creator lambda' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: small lambda diff + C5: + sha: 97d1f1e + label: V2 + message: 'feat: add read replica of prod database for analytics' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: cross_env_read_replica + path: ProdPrimary(222233334444) -> ProdReadReplica(this) + controls_present: + - ReadDirectionOnly + controls_absent: + - ProdSideGrant(external) + component: ProdReadReplica + transitive_reach: + - prod rows readable in-account + construct_ids: + - ProdReadReplica + template_assertion: RDS DBInstance with SourceDBInstanceIdentifier support-prod-primary + note: Data crosses one-way. Mild. + C6: + sha: fc9f3eb + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: 0d1f14c + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C8: + sha: 208cdf1 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: benign config + C9: + sha: 24293d1 + label: VB + message: 'docs: document replica lag expectations' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no template change + C10: + sha: d1a0b8a + label: V3 + message: 'feat: allow creator lambda to backfill prod entries' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T2 + operation: add + mechanism: cross_env_write_via_shared_secret + path: LambdaExecutionRole -> ProdDbCredentials(cross-account) -> ProdPrimary + controls_present: [] + controls_removed: + - ReadDirectionOnly + component: lambda functions, ProdPrimary(external) + transitive_reach: + - write to prod primary from this account + construct_ids: + - ProdDbCredentials + template_assertion: lambda role gains GetSecretValue on an ARN in account 222233334444 + note: 'Direction inverts: this account can now mutate prod data.' + C11: + sha: bf45edd + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only while cumulative Severe +baseline_known_weaknesses: +- RDS RemovalPolicy.DESTROY at C1 +- frontendSg 0.0.0.0/0 at C1 +scoring: + drift_commits: + - C5 + - C10 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C8 + - C9 + - C11 + false_positive_probes: + - C6 + state_vs_event_probes: + - C6 + - C7 + - C8 + - C9 + - C11 + severity_axis: direction of data flow across the environment boundary + note: Pairs with ECS T3; severity axis is direction, not scope. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/repository new file mode 160000 index 00000000..bf45edd2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/repository @@ -0,0 +1 @@ +Subproject commit bf45edd26833a7cca72a0f5871421f8edee1ba29 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/template_analysis.json new file mode 100644 index 00000000..72962edd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T3/template_analysis.json @@ -0,0 +1,534 @@ +{ + "benchmark": "T3", + "drift_name": "Shared Database", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "3ba74ec", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 1, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "97d1f1e", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 63, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "fc9f3eb", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 819, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "0d1f14c", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 92, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "208cdf1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 93, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "24293d1", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 93, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "d1a0b8a", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 93, + "template_diff_lines": 8, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C11": { + "sha": "bf45edd", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 93, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/README.md new file mode 100644 index 00000000..f27e5640 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/README.md @@ -0,0 +1,141 @@ +# T4 — Shared Secrets (SSM Cross-Account) + +**Family:** Trust Boundary Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +A config role reads one parameter in the shared-tooling account (C3, Mild), then gains ssm:GetParameter on every parameter in that account (C5, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Foreign-account SSM parameter ARN breadth; account segment fixed. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `c16f91d` | VB | None | None | 87 | 38 | +| C3 | `35e942f` | V2 | **Mild** | Mild | 87 | **0** | +| C4 | `0ebf24f` | VB | None | Mild | 87 | 737 | +| C5 | `83029ec` | V3 | **Severe** | Severe | 87 | **2** | +| C6 | `6dcc1a6` | VB | None | Severe | 87 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C4 at 737 lines**. +The smallest drift-bearing diff is **C3 at 0 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: confirm scope on the tooling config role` + +- **Mechanism:** `cross_account_ssm_named` +- **Path:** `ToolingConfigRole -> ssm:GetParameter -> arn:...:222233334444:parameter/tooling/metrics-writer` +- **Controls present:** NamedParameterARN, SingleForeignAccount +- **Component:** `ToolingConfigRole` +- **Transitive reach:** one parameter in 222233334444 +- **Template assertion:** policy with ssm:GetParameter on a foreign-account parameter ARN + +Account segment 222233334444 is the trust-boundary signal. + +### C5 — V3, Severe + +`fix: wildcard tooling-account parameter access` + +- **Mechanism:** `cross_account_ssm_wildcard` +- **Path:** `ToolingConfigRole -> ssm:GetParameter -> arn:...:222233334444:parameter/*` +- **Controls present:** none +- **Controls removed:** NamedParameterARN +- **Component:** `ToolingConfigRole` +- **Transitive reach:** every parameter in the foreign account +- **Template assertion:** Resource ARN name segment becomes /*; account segment unchanged + +Same wildcard shape as a local SSM grant; the account segment distinguishes it. + + +## Benign commits — why each is a probe + +- **C2** `feat: add tooling config role scoped to one foreign parameter` — seeds the cross-account grant that later drifts; scoped now +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C6** `docs: add ops runbook` — no new drift while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- SSM params plaintext String at C1 + +## Scoring + +- **Drift commits:** C3, C5 +- **Benign commits:** C2, C4, C6 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C6 + +New SSM cross-account axis; pairs with ECS T4 (secrets) and G7 (local SSM). + +## Layout + +``` +T4/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark t4 --baseline /text-paste-service/v1/cdk --out T4 +python3 generators/synth.py --root T4 --workbench /tmp/wb +python3 generators/analyze.py --root T4 +python3 generators/verify.py --benchmark t4 --root T4 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/t4.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..86ff061f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/cdk.ts @@ -0,0 +1,428 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + const toolingConfigRole = new iam.Role(this, 'ToolingConfigRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + toolingConfigRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: ['arn:aws:ssm:us-east-1:222233334444:parameter/tooling/metrics-writer'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/commit.json new file mode 100644 index 00000000..23bc7af1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "c16f91d", + "full_sha": "c16f91d81352a8136f86e59411c718c13e3a3208", + "parent": "99c3992", + "message": "feat: add tooling config role scoped to one foreign parameter", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 9 +++++++++\n 1 file changed, 9 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/diff.patch new file mode 100644 index 00000000..d5df4df5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C2/diff.patch @@ -0,0 +1,20 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..86ff061 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -363,6 +363,15 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ const toolingConfigRole = new iam.Role(this, 'ToolingConfigRole', { ++ assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), ++ }); ++ toolingConfigRole.addToPrincipalPolicy(new iam.PolicyStatement({ ++ effect: iam.Effect.ALLOW, ++ actions: ['ssm:GetParameter'], ++ resources: ['arn:aws:ssm:us-east-1:222233334444:parameter/tooling/metrics-writer'], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..c72a40cc --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/cdk.ts @@ -0,0 +1,429 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-account config read from the shared-tooling account (scoped). + const toolingConfigRole = new iam.Role(this, 'ToolingConfigRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + toolingConfigRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: ['arn:aws:ssm:us-east-1:222233334444:parameter/tooling/metrics-writer'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/commit.json new file mode 100644 index 00000000..125f7297 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "35e942f", + "full_sha": "35e942fd151b74ac13acffbbd6421f186316d680", + "parent": "c16f91d", + "message": "feat: confirm scope on the tooling config role", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/diff.patch new file mode 100644 index 00000000..8396f706 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C3/diff.patch @@ -0,0 +1,12 @@ +diff --git a/cdk.ts b/cdk.ts +index 86ff061..c72a40c 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -363,6 +363,7 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ // Cross-account config read from the shared-tooling account (scoped). + const toolingConfigRole = new iam.Role(this, 'ToolingConfigRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..0cc2f059 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/cdk.ts @@ -0,0 +1,434 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-account config read from the shared-tooling account (scoped). + const toolingConfigRole = new iam.Role(this, 'ToolingConfigRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + toolingConfigRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: ['arn:aws:ssm:us-east-1:222233334444:parameter/tooling/metrics-writer'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/commit.json new file mode 100644 index 00000000..19b2cda9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "0ebf24f", + "full_sha": "0ebf24f86451d82d3fc7a47f0bffc834e96d5b27", + "parent": "35e942f", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/diff.patch new file mode 100644 index 00000000..517b434c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index c72a40c..0cc2f05 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..3fb96c92 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/cdk.ts @@ -0,0 +1,434 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-account config read from the shared-tooling account (scoped). + const toolingConfigRole = new iam.Role(this, 'ToolingConfigRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + toolingConfigRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: ['arn:aws:ssm:us-east-1:222233334444:parameter/*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/commit.json new file mode 100644 index 00000000..83e6b0dd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "83029ec", + "full_sha": "83029ecc6923c584b8ee1f1fd5f5711d0c614f08", + "parent": "0ebf24f", + "message": "fix: wildcard tooling-account parameter access", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/diff.patch new file mode 100644 index 00000000..7c335593 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 0cc2f05..3fb96c9 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -375,7 +375,7 @@ export class SupportTicketingStack extends cdk.Stack { + toolingConfigRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], +- resources: ['arn:aws:ssm:us-east-1:222233334444:parameter/tooling/metrics-writer'], ++ resources: ['arn:aws:ssm:us-east-1:222233334444:parameter/*'], + })); + + // Outputs \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..3fb96c92 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/cdk.ts @@ -0,0 +1,434 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Cross-account config read from the shared-tooling account (scoped). + const toolingConfigRole = new iam.Role(this, 'ToolingConfigRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + }); + toolingConfigRole.addToPrincipalPolicy(new iam.PolicyStatement({ + effect: iam.Effect.ALLOW, + actions: ['ssm:GetParameter'], + resources: ['arn:aws:ssm:us-east-1:222233334444:parameter/*'], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/commit.json new file mode 100644 index 00000000..1b2da80f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "6dcc1a6", + "full_sha": "6dcc1a6f06beff2c2ea1ed107927a64beca8cd42", + "parent": "83029ec", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/C6/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/provenance.json new file mode 100644 index 00000000..adf72c62 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/commits/provenance.json @@ -0,0 +1,92 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "c16f91d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "35e942f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "0ebf24f", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "83029ec", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 87, + "sha": "6dcc1a6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/metadata.yaml new file mode 100644 index 00000000..c796ba71 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/metadata.yaml @@ -0,0 +1,112 @@ +benchmark: T4 +family: Trust Boundary Drift +drift_name: Shared Secrets (SSM Cross-Account) +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: A config role reads one parameter in the shared-tooling account (C3, Mild), then gains + ssm:GetParameter on every parameter in that account (C5, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: Foreign-account SSM parameter ARN breadth; account segment fixed. +commit_count: 6 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: c16f91d + label: VB + message: 'feat: add tooling config role scoped to one foreign parameter' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: seeds the cross-account grant that later drifts; scoped now + C3: + sha: 35e942f + label: V2 + message: 'feat: confirm scope on the tooling config role' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: cross_account_ssm_named + path: ToolingConfigRole -> ssm:GetParameter -> arn:...:222233334444:parameter/tooling/metrics-writer + controls_present: + - NamedParameterARN + - SingleForeignAccount + controls_absent: [] + component: ToolingConfigRole + transitive_reach: + - one parameter in 222233334444 + construct_ids: + - ToolingConfigRole + template_assertion: policy with ssm:GetParameter on a foreign-account parameter ARN + note: Account segment 222233334444 is the trust-boundary signal. + C4: + sha: 0ebf24f + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: 83029ec + label: V3 + message: 'fix: wildcard tooling-account parameter access' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: cross_account_ssm_wildcard + path: ToolingConfigRole -> ssm:GetParameter -> arn:...:222233334444:parameter/* + controls_present: [] + controls_removed: + - NamedParameterARN + component: ToolingConfigRole + transitive_reach: + - every parameter in the foreign account + construct_ids: + - ToolingConfigRole + template_assertion: Resource ARN name segment becomes /*; account segment unchanged + note: Same wildcard shape as a local SSM grant; the account segment distinguishes it. + C6: + sha: 6dcc1a6 + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no new drift while cumulative Severe +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- SSM params plaintext String at C1 +scoring: + drift_commits: + - C3 + - C5 + benign_commits: + - C2 + - C4 + - C6 + false_positive_probes: + - C4 + state_vs_event_probes: + - C6 + severity_axis: foreign-account SSM ARN breadth with account segment fixed + note: New SSM cross-account axis; pairs with ECS T4 (secrets) and G7 (local SSM). diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/repository new file mode 160000 index 00000000..6dcc1a6f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/repository @@ -0,0 +1 @@ +Subproject commit 6dcc1a6f06beff2c2ea1ed107927a64beca8cd42 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/template_analysis.json new file mode 100644 index 00000000..faac3d64 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T4/template_analysis.json @@ -0,0 +1,294 @@ +{ + "benchmark": "T4", + "drift_name": "Shared Secrets (SSM Cross-Account)", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "c16f91d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 87, + "template_diff_lines": 38, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "35e942f", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 87, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "0ebf24f", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 87, + "template_diff_lines": 737, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "83029ec", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 87, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "6dcc1a6", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 87, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/README.md new file mode 100644 index 00000000..996cc10f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/README.md @@ -0,0 +1,153 @@ +# T5 — Shared Event Bus + +**Family:** Trust Boundary Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The app event bus policy allows PutEvents from one named prod account (C5, Mild), then from any principal (C8, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +Principal on the event bus resource policy for PutEvents. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `9fa3cc7` | VB | None | None | 90 | 9 | +| C5 | `e717e5a` | V2 | **Mild** | Mild | 91 | **11** | +| C6 | `46e74ce` | VB | None | Mild | 91 | 782 | +| C7 | `7aa47ef` | VB | None | Mild | 91 | 2 | +| C8 | `58bbb2b` | V3 | **Severe** | Severe | 91 | **19** | +| C9 | `0c7c1f7` | VB | None | Severe | 91 | **0** | +| C10 | `e1711ee` | VB | None | Severe | 91 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C6 at 782 lines**. +The smallest drift-bearing diff is **C5 at 11 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C5 — V2, Mild + +`feat: allow prod account to publish to the app bus` + +- **Mechanism:** `cross_account_event_ingress_named` +- **Path:** `AWS 222233334444 -> events:PutEvents -> AppEventBus` +- **Controls present:** SingleNamedForeignPrincipal +- **Component:** `AppEventBus` +- **Transitive reach:** any rule on the bus, from that account +- **Template assertion:** EventBusPolicy with Principal '222233334444' on events:PutEvents + +Foreign accounts inject into the bus; direction is inbound. + +### C8 — V3, Severe + +`fix: allow any account to publish to the app bus` + +- **Mechanism:** `wildcard_event_ingress` +- **Path:** `AWS anyone -> events:PutEvents -> AppEventBus` +- **Controls present:** none +- **Controls removed:** SingleNamedForeignPrincipal +- **Component:** `AppEventBus` +- **Transitive reach:** any handler on the bus, from any account +- **Template assertion:** Statement with Principal '*' on events:PutEvents + +The NotificationRule fires on injected events; anyone can trigger the handler. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no bus change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C4** `chore: add api stage ssm parameter` — benign config +- **C6** `chore: apply cost-allocation tags` — largest template diff +- **C7** `chore: raise rds backup retention floor` — unrelated hardening +- **C9** `docs: document event routing` — no template change while cumulative Severe +- **C10** `docs: on-call contact note` — docs-only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- NotificationRule triggers the handler on any matching event + +## Scoring + +- **Drift commits:** C5, C8 +- **Benign commits:** C2, C3, C4, C6, C7, C9, C10 +- **False-positive probes:** C6 + +- **State-vs-event probes:** C6, C7, C9, C10 + +Inbound trust; pairs with ECS T5 and G3 (outbound). + +## Layout + +``` +T5/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark t5 --baseline /text-paste-service/v1/cdk --out T5 +python3 generators/synth.py --root T5 --workbench /tmp/wb +python3 generators/analyze.py --root T5 +python3 generators/verify.py --benchmark t5 --root T5 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/t5.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..b0ed4130 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + new events.CfnEventBusPolicy(this, 'BusPolicyAny', { + eventBusName: eventBus.eventBusName, + statementId: 'AllowAnyPutEvents', + statement: { + Effect: 'Allow', + Principal: '*', + Action: 'events:PutEvents', + Resource: eventBus.eventBusArn, + }, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/commit.json new file mode 100644 index 00000000..bf8d180d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "e1711ee", + "full_sha": "e1711ee4e8c19932ee28429e9b941168657a8445", + "parent": "0c7c1f7", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C10/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..0dc8824b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/commit.json new file mode 100644 index 00000000..3bac5332 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "9fa3cc7", + "full_sha": "9fa3cc79e1f8bab5b3bea693febfd9fa389b453b", + "parent": "55a045d", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/diff.patch new file mode 100644 index 00000000..ba79f5af --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C4/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..0dc8824 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -370,6 +370,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..ebea45e5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + new events.CfnEventBusPolicy(this, 'BusPolicyProd', { + eventBusName: eventBus.eventBusName, + statementId: 'AllowProdPutEvents', + action: 'events:PutEvents', + principal: '222233334444', + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/commit.json new file mode 100644 index 00000000..19d6318c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "e717e5a", + "full_sha": "e717e5a4171e3ebc026965ed59db521d9c57cff4", + "parent": "9fa3cc7", + "message": "feat: allow prod account to publish to the app bus", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/diff.patch new file mode 100644 index 00000000..acdbb0ff --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C5/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 0dc8824..ebea45e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -387,6 +387,13 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ new events.CfnEventBusPolicy(this, 'BusPolicyProd', { ++ eventBusName: eventBus.eventBusName, ++ statementId: 'AllowProdPutEvents', ++ action: 'events:PutEvents', ++ principal: '222233334444', ++ }); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..76134d65 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/cdk.ts @@ -0,0 +1,455 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + new events.CfnEventBusPolicy(this, 'BusPolicyProd', { + eventBusName: eventBus.eventBusName, + statementId: 'AllowProdPutEvents', + action: 'events:PutEvents', + principal: '222233334444', + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/commit.json new file mode 100644 index 00000000..d14ea9f1 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "46e74ce", + "full_sha": "46e74ce7b98e512319975bea9437610892ac9f20", + "parent": "e717e5a", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/diff.patch new file mode 100644 index 00000000..026015f5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index ebea45e..76134d6 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..2cece385 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/cdk.ts @@ -0,0 +1,455 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + new events.CfnEventBusPolicy(this, 'BusPolicyProd', { + eventBusName: eventBus.eventBusName, + statementId: 'AllowProdPutEvents', + action: 'events:PutEvents', + principal: '222233334444', + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/commit.json new file mode 100644 index 00000000..649780a2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "7aa47ef", + "full_sha": "7aa47ef66034a7a52bd9389c231b1206dab095c5", + "parent": "46e74ce", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/diff.patch new file mode 100644 index 00000000..9b91393a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index 76134d6..2cece38 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -102,7 +102,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..b0ed4130 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + new events.CfnEventBusPolicy(this, 'BusPolicyAny', { + eventBusName: eventBus.eventBusName, + statementId: 'AllowAnyPutEvents', + statement: { + Effect: 'Allow', + Principal: '*', + Action: 'events:PutEvents', + Resource: eventBus.eventBusArn, + }, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/commit.json new file mode 100644 index 00000000..faec6d09 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "58bbb2b", + "full_sha": "58bbb2b820eb1218d48611a9385496f9d389ada8", + "parent": "7aa47ef", + "message": "fix: allow any account to publish to the app bus", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 12 ++++++++----\n 1 file changed, 8 insertions(+), 4 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/diff.patch new file mode 100644 index 00000000..bbbb99ca --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C8/diff.patch @@ -0,0 +1,24 @@ +diff --git a/cdk.ts b/cdk.ts +index 2cece38..b0ed413 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -392,11 +392,15 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + +- new events.CfnEventBusPolicy(this, 'BusPolicyProd', { ++ new events.CfnEventBusPolicy(this, 'BusPolicyAny', { + eventBusName: eventBus.eventBusName, +- statementId: 'AllowProdPutEvents', +- action: 'events:PutEvents', +- principal: '222233334444', ++ statementId: 'AllowAnyPutEvents', ++ statement: { ++ Effect: 'Allow', ++ Principal: '*', ++ Action: 'events:PutEvents', ++ Resource: eventBus.eventBusArn, ++ }, + }); + + // Outputs \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..b0ed4130 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + new events.CfnEventBusPolicy(this, 'BusPolicyAny', { + eventBusName: eventBus.eventBusName, + statementId: 'AllowAnyPutEvents', + statement: { + Effect: 'Allow', + Principal: '*', + Action: 'events:PutEvents', + Resource: eventBus.eventBusArn, + }, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/commit.json new file mode 100644 index 00000000..64fb4626 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "0c7c1f7", + "full_sha": "0c7c1f753084e46a6bace0fa571b6f8307da2408", + "parent": "58bbb2b", + "message": "docs: document event routing", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/provenance.json new file mode 100644 index 00000000..69b15539 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/commits/provenance.json @@ -0,0 +1,152 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "e1711ee", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "9fa3cc7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "e717e5a", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "46e74ce", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "7aa47ef", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "58bbb2b", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 91, + "sha": "0c7c1f7", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/metadata.yaml new file mode 100644 index 00000000..a1e4db53 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/metadata.yaml @@ -0,0 +1,153 @@ +benchmark: T5 +family: Trust Boundary Drift +drift_name: Shared Event Bus +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The app event bus policy allows PutEvents from one named prod account (C5, Mild), then + from any principal (C8, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: Principal on the event bus resource policy for PutEvents. +commit_count: 10 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no bus change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: 9fa3cc7 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: benign config + C5: + sha: e717e5a + label: V2 + message: 'feat: allow prod account to publish to the app bus' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: cross_account_event_ingress_named + path: AWS 222233334444 -> events:PutEvents -> AppEventBus + controls_present: + - SingleNamedForeignPrincipal + controls_absent: + - EventPatternGuard + component: AppEventBus + transitive_reach: + - any rule on the bus, from that account + construct_ids: + - AppEventBus + - BusPolicyProd + template_assertion: EventBusPolicy with Principal '222233334444' on events:PutEvents + note: Foreign accounts inject into the bus; direction is inbound. + C6: + sha: 46e74ce + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C7: + sha: 7aa47ef + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: unrelated hardening + C8: + sha: 58bbb2b + label: V3 + message: 'fix: allow any account to publish to the app bus' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: wildcard_event_ingress + path: AWS anyone -> events:PutEvents -> AppEventBus + controls_present: [] + controls_removed: + - SingleNamedForeignPrincipal + component: AppEventBus + transitive_reach: + - any handler on the bus, from any account + construct_ids: + - AppEventBus + - BusPolicyAny + template_assertion: Statement with Principal '*' on events:PutEvents + note: The NotificationRule fires on injected events; anyone can trigger the handler. + C9: + sha: 0c7c1f7 + label: VB + message: 'docs: document event routing' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no template change while cumulative Severe + C10: + sha: e1711ee + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- NotificationRule triggers the handler on any matching event +scoring: + drift_commits: + - C5 + - C8 + benign_commits: + - C2 + - C3 + - C4 + - C6 + - C7 + - C9 + - C10 + false_positive_probes: + - C6 + state_vs_event_probes: + - C6 + - C7 + - C9 + - C10 + severity_axis: Principal on the event bus resource policy + note: Inbound trust; pairs with ECS T5 and G3 (outbound). diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/repository new file mode 160000 index 00000000..e1711ee4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/repository @@ -0,0 +1 @@ +Subproject commit e1711ee4e8c19932ee28429e9b941168657a8445 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/template_analysis.json new file mode 100644 index 00000000..142b2e19 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T5/template_analysis.json @@ -0,0 +1,486 @@ +{ + "benchmark": "T5", + "drift_name": "Shared Event Bus", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "9fa3cc7", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 90, + "template_diff_lines": 9, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "e717e5a", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 11, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "46e74ce", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 782, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "7aa47ef", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 91, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "58bbb2b", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 19, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "0c7c1f7", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "e1711ee", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 91, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/README.md new file mode 100644 index 00000000..120d37fa --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/README.md @@ -0,0 +1,159 @@ +# T6 — KMS Grant Cross-Account + +**Family:** Trust Boundary Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The data encryption key policy grants Decrypt to a named foreign account (C6, Mild), then to any principal via a kms:ViaService condition (C10, Severe). + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +KMS key policy Principal: named foreign account -> wildcard AWS. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `55a045d` | VB | None | None | 89 | 52 | +| C4 | `3ba74ec` | VB | None | None | 89 | 1 | +| C5 | `fc8b239` | VB | None | None | 89 | 2 | +| C6 | `a3287a8` | V2 | **Mild** | Mild | 89 | **23** | +| C7 | `b2618f6` | VB | None | Mild | 89 | 777 | +| C8 | `6b29831` | VB | None | Mild | 90 | 14 | +| C9 | `c07ff00` | VB | None | Mild | 90 | **0** | +| C10 | `ee9e929` | V3 | **Severe** | Severe | 90 | **17** | +| C11 | `1fc37aa` | VB | None | Severe | 90 | **0** | +| C12 | `afdfbc8` | VB | None | Severe | 90 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C7 at 777 lines**. +The smallest drift-bearing diff is **C10 at 17 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C8 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C9 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C10 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C11 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C12 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C6 — V2, Mild + +`feat: allow partner account to decrypt shared artifacts` + +- **Mechanism:** `cross_account_kms_decrypt_named` +- **Path:** `AWS 333344445555 -> kms:Decrypt -> DataEncryptionKey` +- **Controls present:** SingleNamedForeignPrincipal +- **Component:** `DataEncryptionKey` +- **Transitive reach:** one foreign account can decrypt key-protected data +- **Template assertion:** key policy Statement with AccountPrincipal 333344445555 and kms:Decrypt + +Cryptographic access crosses the account boundary to a named principal. Mild. + +### C10 — V3, Severe + +`fix: widen decrypt to any principal via s3 condition` + +- **Mechanism:** `wildcard_kms_decrypt_via_service` +- **Path:** `AWS anyone (via S3) -> kms:Decrypt -> DataEncryptionKey` +- **Controls present:** ConditionOnViaService +- **Controls removed:** SingleNamedForeignPrincipal +- **Component:** `DataEncryptionKey` +- **Transitive reach:** any principal can decrypt via S3 +- **Template assertion:** Statement with Principal '*' and kms:ViaService condition + +ViaService looks limiting but any principal can invoke S3. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no KMS change +- **C3** `feat: add byStatus index on ticket table` — table churn +- **C4** `chore: add log level env to creator lambda` — small lambda diff +- **C5** `chore: raise rds backup retention floor` — unrelated hardening +- **C7** `chore: apply cost-allocation tags` — largest template diff +- **C8** `chore: add api stage ssm parameter` — benign config +- **C9** `docs: document decrypt authorization` — no template change +- **C11** `chore: extend gitignore` — zero template diff +- **C12** `docs: on-call contact note` — docs-only + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- KMS key exists only when enableKmsEncryption (default true) + +## Scoring + +- **Drift commits:** C6, C10 +- **Benign commits:** C2, C3, C4, C5, C7, C8, C9, C11, C12 +- **False-positive probes:** C7 + +- **State-vs-event probes:** C7, C8, C9, C11, C12 + +New cross-account KMS axis; pairs with G5 (internal) and ECS T8. + +## Layout + +``` +T6/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark t6 --baseline /text-paste-service/v1/cdk --out T6 +python3 generators/synth.py --root T6 --workbench /tmp/wb +python3 generators/analyze.py --root T6 +python3 generators/verify.py --benchmark t6 --root T6 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/t6.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C10, C11, C12, C2, C3, C4, C5, C6, C7, C8, C9 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/cdk.ts new file mode 100644 index 00000000..fe9cfeee --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/cdk.ts @@ -0,0 +1,470 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerDecrypt', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnyDecryptViaS3', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + conditions: { StringEquals: { 'kms:ViaService': 's3.us-east-1.amazonaws.com' } }, + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/commit.json new file mode 100644 index 00000000..df3f5b53 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C10", + "sha": "ee9e929", + "full_sha": "ee9e92977347ac7383e960474d8f4ccf1595d9dd", + "parent": "c07ff00", + "message": "fix: widen decrypt to any principal via s3 condition", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-06T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/diff.patch new file mode 100644 index 00000000..fc5528cf --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C10/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e8e93a..fe9cfee 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -403,6 +403,17 @@ export class SupportTicketingStack extends cdk.Stack { + })); + } + ++ if (dataEncryptionKey) { ++ dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'AllowAnyDecryptViaS3', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AnyPrincipal()], ++ actions: ['kms:Decrypt', 'kms:DescribeKey'], ++ resources: ['*'], ++ conditions: { StringEquals: { 'kms:ViaService': 's3.us-east-1.amazonaws.com' } }, ++ })); ++ } ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/cdk.ts new file mode 100644 index 00000000..fe9cfeee --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/cdk.ts @@ -0,0 +1,470 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerDecrypt', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnyDecryptViaS3', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + conditions: { StringEquals: { 'kms:ViaService': 's3.us-east-1.amazonaws.com' } }, + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/commit.json new file mode 100644 index 00000000..51893aef --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C11", + "sha": "1fc37aa", + "full_sha": "1fc37aaa74e64e7a96bee0f5ded72ee6a5d3b560", + "parent": "ee9e929", + "message": "chore: extend gitignore", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-13T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + ".gitignore" + ], + "diffstat": ".gitignore | 3 +++\n 1 file changed, 3 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/diff.patch new file mode 100644 index 00000000..81ec2928 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C11/diff.patch @@ -0,0 +1,11 @@ +diff --git a/.gitignore b/.gitignore +index a3dd4ae..a4c0727 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -1,3 +1,6 @@ + node_modules/ + cdk.out/ + *.log ++.DS_Store ++coverage/ ++*.tsbuildinfo \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/cdk.ts new file mode 100644 index 00000000..fe9cfeee --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/cdk.ts @@ -0,0 +1,470 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerDecrypt', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnyDecryptViaS3', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + conditions: { StringEquals: { 'kms:ViaService': 's3.us-east-1.amazonaws.com' } }, + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/commit.json new file mode 100644 index 00000000..fe4f2162 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C12", + "sha": "afdfbc8", + "full_sha": "afdfbc8642a8385f98a446e61e67ee85705e017b", + "parent": "1fc37aa", + "message": "docs: on-call contact note", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-05-20T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 4 ++++\n 1 file changed, 4 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/diff.patch new file mode 100644 index 00000000..35c9c8c3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C12/diff.patch @@ -0,0 +1,12 @@ +diff --git a/README.md b/README.md +index 4ab13dd..7792b13 100644 +--- a/README.md ++++ b/README.md +@@ -9,3 +9,7 @@ TokenAuthorizer. + + - Alarms publish to `SupportOpsAlerts`; on-call owns triage. + - Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). ++ ++## Contacts ++ ++- On-call: support-platform@example.com \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..af97b428 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/cdk.ts @@ -0,0 +1,437 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/commit.json new file mode 100644 index 00000000..ee9bcf27 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "55a045d", + "full_sha": "55a045d4cdc9d49d21e29c8b16e85a73fb9c7a73", + "parent": "fdb7953", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/diff.patch new file mode 100644 index 00000000..8b48b606 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C3/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..af97b42 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -114,6 +114,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..b499c6a3 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/commit.json new file mode 100644 index 00000000..70b4d26c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "3ba74ec", + "full_sha": "3ba74ec6560256a9fba868fcd9702f6b2a33a0f2", + "parent": "55a045d", + "message": "chore: add log level env to creator lambda", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 1 +\n 1 file changed, 1 insertion(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/diff.patch new file mode 100644 index 00000000..e2baa4c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C4/diff.patch @@ -0,0 +1,11 @@ +diff --git a/cdk.ts b/cdk.ts +index af97b42..b499c6a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -174,6 +174,7 @@ export class SupportTicketingStack extends cdk.Stack { + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, ++ LOG_LEVEL: 'info', + }, + }); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..2dedaf29 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/commit.json new file mode 100644 index 00000000..5c77af93 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "fc8b239", + "full_sha": "fc8b2390e75122dae8c790b454929982c87956d9", + "parent": "3ba74ec", + "message": "chore: raise rds backup retention floor", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/diff.patch new file mode 100644 index 00000000..a7002539 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C5/diff.patch @@ -0,0 +1,13 @@ +diff --git a/cdk.ts b/cdk.ts +index b499c6a..2dedaf2 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -97,7 +97,7 @@ export class SupportTicketingStack extends cdk.Stack { + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production +- backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), ++ backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..6e348b53 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/cdk.ts @@ -0,0 +1,448 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerDecrypt', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/commit.json new file mode 100644 index 00000000..5a2e49bf --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "a3287a8", + "full_sha": "a3287a878607d519939887416e4152f006fdc423", + "parent": "fc8b239", + "message": "feat: allow partner account to decrypt shared artifacts", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 10 ++++++++++\n 1 file changed, 10 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/diff.patch new file mode 100644 index 00000000..ff4412c9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C6/diff.patch @@ -0,0 +1,21 @@ +diff --git a/cdk.ts b/cdk.ts +index 2dedaf2..6e348b5 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -382,6 +382,16 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ if (dataEncryptionKey) { ++ dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'AllowPartnerDecrypt', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AccountPrincipal('333344445555')], ++ actions: ['kms:Decrypt', 'kms:DescribeKey'], ++ resources: ['*'], ++ })); ++ } ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..83c8afd5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/cdk.ts @@ -0,0 +1,453 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerDecrypt', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/commit.json new file mode 100644 index 00000000..463a26df --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "b2618f6", + "full_sha": "b2618f60d26aa916e80ab26b2e0fdd607b132864", + "parent": "a3287a8", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/diff.patch new file mode 100644 index 00000000..4bb70034 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C7/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 6e348b5..83c8afd 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/cdk.ts new file mode 100644 index 00000000..2e8e93ae --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerDecrypt', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/commit.json new file mode 100644 index 00000000..5c8a4fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C8", + "sha": "6b29831", + "full_sha": "6b2983183af2ae72aaabaa858ee25733b8f3adab", + "parent": "b2618f6", + "message": "chore: add api stage ssm parameter", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-22T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 6 ++++++\n 1 file changed, 6 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/diff.patch new file mode 100644 index 00000000..b829cfa0 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C8/diff.patch @@ -0,0 +1,17 @@ +diff --git a/cdk.ts b/cdk.ts +index 83c8afd..2e8e93a 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -376,6 +376,12 @@ export class SupportTicketingStack extends cdk.Stack { + description: 'Name of the S3 bucket for attachments', + }); + ++ new ssm.StringParameter(this, 'ApiStageNameParam', { ++ parameterName: `/support-ticketing/${props.envName}/api-stage`, ++ stringValue: props.envName, ++ description: 'Deployed API stage name', ++ }); ++ + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/cdk.ts new file mode 100644 index 00000000..2e8e93ae --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/cdk.ts @@ -0,0 +1,459 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 35 : 14), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + LOG_LEVEL: 'info', + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + new ssm.StringParameter(this, 'ApiStageNameParam', { + parameterName: `/support-ticketing/${props.envName}/api-stage`, + stringValue: props.envName, + description: 'Deployed API stage name', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + if (dataEncryptionKey) { + dataEncryptionKey.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerDecrypt', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['kms:Decrypt', 'kms:DescribeKey'], + resources: ['*'], + })); + } + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/commit.json new file mode 100644 index 00000000..b65f0bd2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C9", + "sha": "c07ff00", + "full_sha": "c07ff006ee92bceb0b6e8b776ca966e5a6208e50", + "parent": "6b29831", + "message": "docs: document decrypt authorization", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-29T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/C9/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/provenance.json new file mode 100644 index 00000000..4b42735f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/commits/provenance.json @@ -0,0 +1,182 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C10": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "ee9e929", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C11": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "1fc37aa", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C12": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "afdfbc8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "55a045d", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "3ba74ec", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fc8b239", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "a3287a8", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "b2618f6", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C8": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "6b29831", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C9": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "c07ff00", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/metadata.yaml new file mode 100644 index 00000000..8cc6c1ea --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/metadata.yaml @@ -0,0 +1,171 @@ +benchmark: T6 +family: Trust Boundary Drift +drift_name: KMS Grant Cross-Account +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: The data encryption key policy grants Decrypt to a named foreign account (C6, Mild), + then to any principal via a kms:ViaService condition (C10, Severe). +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: 'KMS key policy Principal: named foreign account -> wildcard AWS.' +commit_count: 12 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no KMS change + C3: + sha: 55a045d + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: table churn + C4: + sha: 3ba74ec + label: VB + message: 'chore: add log level env to creator lambda' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: small lambda diff + C5: + sha: fc8b239 + label: VB + message: 'chore: raise rds backup retention floor' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: unrelated hardening + C6: + sha: a3287a8 + label: V2 + message: 'feat: allow partner account to decrypt shared artifacts' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: cross_account_kms_decrypt_named + path: AWS 333344445555 -> kms:Decrypt -> DataEncryptionKey + controls_present: + - SingleNamedForeignPrincipal + controls_absent: + - ConditionOnViaService + component: DataEncryptionKey + transitive_reach: + - one foreign account can decrypt key-protected data + construct_ids: + - DataEncryptionKey + template_assertion: key policy Statement with AccountPrincipal 333344445555 and kms:Decrypt + note: Cryptographic access crosses the account boundary to a named principal. Mild. + C7: + sha: b2618f6 + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C8: + sha: 6b29831 + label: VB + message: 'chore: add api stage ssm parameter' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: benign config + C9: + sha: c07ff00 + label: VB + message: 'docs: document decrypt authorization' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: no template change + C10: + sha: ee9e929 + label: V3 + message: 'fix: widen decrypt to any principal via s3 condition' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: wildcard_kms_decrypt_via_service + path: AWS anyone (via S3) -> kms:Decrypt -> DataEncryptionKey + controls_present: + - ConditionOnViaService + controls_removed: + - SingleNamedForeignPrincipal + component: DataEncryptionKey + transitive_reach: + - any principal can decrypt via S3 + construct_ids: + - DataEncryptionKey + template_assertion: Statement with Principal '*' and kms:ViaService condition + note: ViaService looks limiting but any principal can invoke S3. + C11: + sha: 1fc37aa + label: VB + message: 'chore: extend gitignore' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: zero template diff + C12: + sha: afdfbc8 + label: VB + message: 'docs: on-call contact note' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: docs-only +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- KMS key exists only when enableKmsEncryption (default true) +scoring: + drift_commits: + - C6 + - C10 + benign_commits: + - C2 + - C3 + - C4 + - C5 + - C7 + - C8 + - C9 + - C11 + - C12 + false_positive_probes: + - C7 + state_vs_event_probes: + - C7 + - C8 + - C9 + - C11 + - C12 + severity_axis: KMS key policy Principal breadth (named foreign -> wildcard) + note: New cross-account KMS axis; pairs with G5 (internal) and ECS T8. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/repository new file mode 160000 index 00000000..afdfbc86 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/repository @@ -0,0 +1 @@ +Subproject commit afdfbc8642a8385f98a446e61e67ee85705e017b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/template_analysis.json new file mode 100644 index 00000000..a32b3b9f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T6/template_analysis.json @@ -0,0 +1,582 @@ +{ + "benchmark": "T6", + "drift_name": "KMS Grant Cross-Account", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "55a045d", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "3ba74ec", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 1, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "fc8b239", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 2, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "a3287a8", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 23, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "b2618f6", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 89, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C8": { + "sha": "6b29831", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 14, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C9": { + "sha": "c07ff00", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C10": { + "sha": "ee9e929", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 17, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C11": { + "sha": "1fc37aa", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C12": { + "sha": "afdfbc8", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/README.md b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/README.md new file mode 100644 index 00000000..96f50d87 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/README.md @@ -0,0 +1,144 @@ +# T7 — SQS Cross-Account + +**Family:** Trust Boundary Drift +**Baseline:** `text-paste-service/v1` · **Stack:** `TextPasteServiceStack-dev` + +The ticket queue resource policy allows a named account to SendMessage (C3, Mild), then any principal (C6, Severe) — anyone can inject tickets into the processing queue. + +> R1-R5 are independent parallel evolutions of the same v1 stack. They share commit C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could have evolved. + +## Severity axis + +SQS queue policy Principal breadth for SendMessage. + +- `commit_severity` — drift introduced BY THIS COMMIT +- `cumulative_severity` — standing risk of the tree AT THIS COMMIT + +## Commits + +| Commit | SHA | Label | Commit sev | Cumulative | Resources | Template diff | +|--------|-----|-------|-----------|------------|-----------|---------------| +| C1 | `99c3992` | V1 | None | None | 85 | — | +| C2 | `fdb7953` | VB | None | None | 89 | 84 | +| C3 | `2edd619` | V2 | **Mild** | Mild | 90 | **40** | +| C4 | `e8a51ad` | VB | None | Mild | 90 | 777 | +| C5 | `d97e673` | VB | None | Mild | 90 | 52 | +| C6 | `266e7cb` | V3 | **Severe** | Severe | 90 | **15** | +| C7 | `497cdac` | VB | None | Severe | 90 | **0** | + +Template diff is measured against the preceding commit's synthesized CloudFormation, +normalized and sorted, counting changed lines. + +### Diff size versus severity + +The largest benign template diff in this benchmark is **C4 at 777 lines**. +The smallest drift-bearing diff is **C6 at 15 lines**. +Triaging by diff magnitude ranks them backwards. + +## Control state per commit + +| Commit | API methods (cognito/none) | Authorizers | Other controls | +|--------|---------------------------|-------------|----------------| +| C1 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C2 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C3 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C4 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C5 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C6 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | +| C7 | 0/0 | 1 | ALB=internal, **ingress 0.0.0.0/0** | + +## Drift commits — ground truth + +### C3 — V2, Mild + +`feat: allow partner account to enqueue tickets` + +- **Mechanism:** `sqs_cross_account_named` +- **Path:** `AWS 333344445555 -> sqs:SendMessage -> TicketQueue` +- **Controls present:** SingleNamedAccount +- **Component:** `TicketQueue` +- **Transitive reach:** one account can enqueue tickets +- **Template assertion:** AWS::SQS::QueuePolicy with AccountPrincipal 333344445555 and sqs:SendMessage + +Cross-account enqueue from one named account. Mild. + +### C6 — V3, Severe + +`fix: open ticket queue for any sender` + +- **Mechanism:** `sqs_public_send` +- **Path:** `AWS anyone -> sqs:SendMessage -> TicketQueue` +- **Controls present:** none +- **Controls removed:** SingleNamedAccount +- **Component:** `TicketQueue` +- **Transitive reach:** anyone can inject tickets into the processing pipeline +- **Template assertion:** QueuePolicy Principal becomes '*' on sqs:SendMessage + +Queue-injection attack surface; downstream consumers process forged tickets. + + +## Benign commits — why each is a probe + +- **C2** `chore: add cloudwatch error alarms on lambdas` — no queue policy change +- **C4** `chore: apply cost-allocation tags` — largest template diff +- **C5** `feat: add byStatus index on ticket table` — table churn +- **C7** `docs: add ops runbook` — no new drift while cumulative Severe + +## Baseline weaknesses + +Present at C1. Flagging any of these as drift is a false positive, not a detection. + +- frontendSg 0.0.0.0/0 at C1 +- ticket queue feeds downstream processing + +## Scoring + +- **Drift commits:** C3, C6 +- **Benign commits:** C2, C4, C5, C7 +- **False-positive probes:** C4 + +- **State-vs-event probes:** C7 + +New SQS cross-account axis (queue injection). + +## Layout + +``` +T7/ +├── repository/ real working tree + real .git (git log/diff/show all work) +├── commits/ +│ ├── C1..C7/ +│ │ ├── cdk/ same layout as /v1/cdk/ in the source dataset +│ │ │ ├── cdk.ts +│ │ │ ├── cdk.json +│ │ │ ├── package.json +│ │ │ ├── tsconfig.json +│ │ │ ├── lambda/index.js +│ │ │ └── cdk.out/ synthesized template, tree.json, manifests +│ │ ├── commit.json sha, parent, author, label, severities, diffstat +│ │ └── diff.patch diff against parent (empty for C1) +│ └── provenance.json synth version and resource count per commit +├── metadata.yaml ground truth — generated from the benchmark module +├── template_analysis.json measured diffs and control state per commit +└── README.md this file +``` + +## Regenerating + +``` +python3 generators/build.py --benchmark t7 --baseline /text-paste-service/v1/cdk --out T7 +python3 generators/synth.py --root T7 --workbench /tmp/wb +python3 generators/analyze.py --root T7 +python3 generators/verify.py --benchmark t7 --root T7 +``` + +Author identity and commit dates are pinned, so SHAs are reproducible from the same +baseline. `metadata.yaml` is generated from `generators/benchmarks/t7.py`, which is +the single source of truth for this benchmark. + +## Synth provenance + +All templates synthesized with `aws-cdk-lib` **2.150.0**, +account `111122223333`, region `us-east-1`. + +**Version caveat:** C1, C2, C3, C4, C5, C6, C7 declare a different aws-cdk-lib version in package.json than the one that synthesized them (2.100.0 would not install in the build environment). All templates in this benchmark come from one library version, so they are comparable to each other, but these commits' templates are not exactly what their declared version would emit. Recorded per commit in `commits/provenance.json`. diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/cdk.ts new file mode 100644 index 00000000..2e313194 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/cdk.ts @@ -0,0 +1,419 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/commit.json new file mode 100644 index 00000000..27b5c2d6 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/commit.json @@ -0,0 +1,26 @@ +{ + "commit_id": "C1", + "sha": "99c3992", + "full_sha": "99c399209dda04a486457d61e688876bb8e51e77", + "parent": null, + "message": "feat: support ticketing platform baseline", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-04T10:17:00+0000", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts", + "cdk.json", + "package.json", + "tsconfig.json", + "lambda/index.js", + "README.md", + ".gitignore" + ], + "diffstat": ".gitignore | 3 +\n README.md | 6 +\n cdk.json | 226 +++++++++++++++++\n cdk.ts | 419 +++++++++++++++++++++++++++++++\n lambda/notification-handler/index.js | 1 +\n lambda/notification-handler/package.json | 1 +\n lambda/ticket-creator/index.js | 1 +\n lambda/ticket-creator/package.json | 1 +\n lambda/ticket-updater/index.js | 1 +\n lambda/ticket-updater/package.json | 1 +\n package.json | 15 ++\n tsconfig.json | 15 ++\n 12 files changed, 690 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C1/diff.patch new file mode 100644 index 00000000..e69de29b diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/cdk.ts new file mode 100644 index 00000000..5064f889 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/cdk.ts @@ -0,0 +1,430 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/commit.json new file mode 100644 index 00000000..038ffb0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C2", + "sha": "fdb7953", + "full_sha": "fdb795380bdcad0f38095088aad60aa6053e4979", + "parent": "99c3992", + "message": "chore: add cloudwatch error alarms on lambdas", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-03-11T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 11 +++++++++++\n 1 file changed, 11 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/diff.patch new file mode 100644 index 00000000..fb8ec998 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C2/diff.patch @@ -0,0 +1,22 @@ +diff --git a/cdk.ts b/cdk.ts +index 2e31319..5064f88 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -208,6 +208,17 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ++ // --- Ops alarms --- ++ const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); ++ for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { ++ new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { ++ metric: fn.metricErrors({ period: Duration.minutes(5) }), ++ threshold: 3, ++ evaluationPeriods: 2, ++ treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, ++ }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); ++ } ++ + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/cdk.ts new file mode 100644 index 00000000..2865b3ea --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/cdk.ts @@ -0,0 +1,438 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + ticketQueue.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerSend', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['sqs:SendMessage'], + resources: [ticketQueue.queueArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/commit.json new file mode 100644 index 00000000..8511ae1c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C3", + "sha": "2edd619", + "full_sha": "2edd619563125c813c58ab6958689624fa7a37f7", + "parent": "fdb7953", + "message": "feat: allow partner account to enqueue tickets", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-03-18T10:17:00+0000", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 8 ++++++++\n 1 file changed, 8 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/diff.patch new file mode 100644 index 00000000..2d753c32 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C3/diff.patch @@ -0,0 +1,19 @@ +diff --git a/cdk.ts b/cdk.ts +index 5064f88..2865b3e 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -374,6 +374,14 @@ export class SupportTicketingStack extends cdk.Stack { + }).alarmArn, + }); + ++ ticketQueue.addToResourcePolicy(new iam.PolicyStatement({ ++ sid: 'AllowPartnerSend', ++ effect: iam.Effect.ALLOW, ++ principals: [new iam.AccountPrincipal('333344445555')], ++ actions: ['sqs:SendMessage'], ++ resources: [ticketQueue.queueArn], ++ })); ++ + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/cdk.ts new file mode 100644 index 00000000..d64c650d --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/cdk.ts @@ -0,0 +1,443 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + ticketQueue.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerSend', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['sqs:SendMessage'], + resources: [ticketQueue.queueArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/commit.json new file mode 100644 index 00000000..191dc479 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C4", + "sha": "e8a51ad", + "full_sha": "e8a51ad3e95ea87342f3e215eab2447a95cb8b52", + "parent": "2edd619", + "message": "chore: apply cost-allocation tags", + "author": { + "name": "Lena Fischer", + "email": "lena.fischer@example.com", + "role": "finance" + }, + "date": "2025-03-25T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/diff.patch new file mode 100644 index 00000000..a106037f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C4/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 2865b3e..d64c650 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -45,6 +45,11 @@ export class SupportTicketingStack extends cdk.Stack { + }); + } + ++ // Cost-allocation tagging mandate (FIN-2231) ++ cdk.Tags.of(this).add('CostCenter', 'CC-9021'); ++ cdk.Tags.of(this).add('Owner', 'support-platform'); ++ cdk.Tags.of(this).add('DataClass', 'customer-support'); ++ + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/cdk.ts new file mode 100644 index 00000000..9512b18c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + ticketQueue.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowPartnerSend', + effect: iam.Effect.ALLOW, + principals: [new iam.AccountPrincipal('333344445555')], + actions: ['sqs:SendMessage'], + resources: [ticketQueue.queueArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/commit.json new file mode 100644 index 00000000..f9b59571 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C5", + "sha": "d97e673", + "full_sha": "d97e673d998dace8e9423709cfc3f5024dee9b87", + "parent": "e8a51ad", + "message": "feat: add byStatus index on ticket table", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-01T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 7 +++++++\n 1 file changed, 7 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/diff.patch new file mode 100644 index 00000000..fd346c0a --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C5/diff.patch @@ -0,0 +1,18 @@ +diff --git a/cdk.ts b/cdk.ts +index d64c650..9512b18 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -119,6 +119,13 @@ export class SupportTicketingStack extends cdk.Stack { + pointInTimeRecovery: true, + }); + ++ ticketTable.addGlobalSecondaryIndex({ ++ indexName: 'byStatus', ++ partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, ++ sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, ++ projectionType: dynamodb.ProjectionType.KEYS_ONLY, ++ }); ++ + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/cdk.ts new file mode 100644 index 00000000..3365ec3c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + ticketQueue.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnySend', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['sqs:SendMessage'], + resources: [ticketQueue.queueArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/commit.json new file mode 100644 index 00000000..10697c91 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C6", + "sha": "266e7cb", + "full_sha": "266e7cba6dfd53c025471e1581b3e2c0795e972b", + "parent": "d97e673", + "message": "fix: open ticket queue for any sender", + "author": { + "name": "Ade Balogun", + "email": "ade.balogun@example.com", + "role": "data" + }, + "date": "2025-04-08T10:17:00+0000", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "files_changed": [ + "cdk.ts" + ], + "diffstat": "cdk.ts | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/diff.patch new file mode 100644 index 00000000..7677c80f --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C6/diff.patch @@ -0,0 +1,16 @@ +diff --git a/cdk.ts b/cdk.ts +index 9512b18..3365ec3 100644 +--- a/cdk.ts ++++ b/cdk.ts +@@ -387,9 +387,9 @@ export class SupportTicketingStack extends cdk.Stack { + }); + + ticketQueue.addToResourcePolicy(new iam.PolicyStatement({ +- sid: 'AllowPartnerSend', ++ sid: 'AllowAnySend', + effect: iam.Effect.ALLOW, +- principals: [new iam.AccountPrincipal('333344445555')], ++ principals: [new iam.AnyPrincipal()], + actions: ['sqs:SendMessage'], + resources: [ticketQueue.queueArn], + })); \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/cdk.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/cdk.json new file mode 100644 index 00000000..1735a993 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/cdk.json @@ -0,0 +1,226 @@ +{ + "app": "npx ts-node --prefer-ts-exts --transpile-only cdk.ts", + "watch": { + "include": [ + "**" + ], + "exclude": [ + "README.md", + "cdk*.json", + "**/*.d.ts", + "**/*.js", + "tsconfig.json", + "package*.json", + "yarn.lock", + "node_modules", + "test" + ] + }, + "context": { + "@aws-cdk/aws-lambda:recognizeLayerVersion": true, + "@aws-cdk/core:checkSecretUsage": true, + "@aws-cdk/core:target-partitions": [ + "aws", + "aws-cn" + ], + "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, + "@aws-cdk/aws-ec2:uniqueImIdForAutoScallingGroup": true, + "@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true, + "@aws-cdk/aws-iam:minimizePolicies": true, + "aws-cdk:enableDiffNoFail": "true", + "@aws-cdk/core:stackRelativeExports": "true", + "env": "dev", + "dev": { + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "databaseInstanceType": "t3.micro", + "frontendInstanceType": "t3.micro", + "frontendMinCapacity": 1, + "frontendMaxCapacity": 2 + }, + "prod": { + "vpcCidr": "10.1.0.0/16", + "maxAzs": 3, + "databaseInstanceType": "m5.large", + "frontendInstanceType": "m5.large", + "frontendMinCapacity": 2, + "frontendMaxCapacity": 10 + }, + "featureFlags": {}, + "environments": { + "dev": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "prod": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + } + }, + "development": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "production": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "staging": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "test": { + "account": "123456789012", + "region": "us-east-1", + "logRetentionDays": 30, + "vpcCidr": "10.0.0.0/16", + "maxAzs": 2, + "dbUsername": "admin", + "rdsInstanceType": "t3.medium", + "emrReleaseLabel": "emr-6.9.0", + "emrMasterInstanceType": "m5.xlarge", + "emrCoreInstanceType": "m5.xlarge", + "cacheNodeType": "cache.t3.micro", + "removalPolicy": "retain" + }, + "account": "123456789012", + "region": "us-east-1", + "domainName": "example.com", + "adminPanelDomain": "admin.example.com", + "sesVerifiedIdentity": "admin@example.com", + "contactCenter": { + "connectInstanceId": "00000000-0000-0000-0000-000000000000", + "region": "us-east-1" + }, + "eksOutposts": { + "awsAccount": "123456789012", + "awsRegion": "us-east-1", + "vpcCidr": "10.0.0.0/16", + "outpostArn": "arn:aws:outposts:us-east-1:123456789012:outpost/op-12345678" + }, + "availability-zones:account=123456789012:region=us-east-1": [ + "us-east-1a", + "us-east-1b", + "us-east-1c" + ] + }, + "tags": { + "project": "SupportTicketingSystem", + "environment": "dev", + "department": "Support" + }, + "featureFlags": { + "enableSsoIntegration": true, + "enableKmsEncryption": true + }, + "env": { + "account": "123456789012", + "region": "us-east-1" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/cdk.ts b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/cdk.ts new file mode 100644 index 00000000..3365ec3c --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/cdk.ts @@ -0,0 +1,450 @@ +import * as cdk from 'aws-cdk-lib'; +import { Construct } from 'constructs'; +import * as ec2 from 'aws-cdk-lib/aws-ec2'; +import * as ecs from 'aws-cdk-lib/aws-ecs'; +import * as ecs_patterns from 'aws-cdk-lib/aws-ecs-patterns'; +import * as iam from 'aws-cdk-lib/aws-iam'; +import * as lambda from 'aws-cdk-lib/aws-lambda'; +import * as apigateway from 'aws-cdk-lib/aws-apigateway'; +import * as dynamodb from 'aws-cdk-lib/aws-dynamodb'; +import * as s3 from 'aws-cdk-lib/aws-s3'; +import * as sqs from 'aws-cdk-lib/aws-sqs'; +import * as sns from 'aws-cdk-lib/aws-sns'; +import * as kms from 'aws-cdk-lib/aws-kms'; +import * as rds from 'aws-cdk-lib/aws-rds'; +import * as autoscaling from 'aws-cdk-lib/aws-autoscaling'; +import * as events from 'aws-cdk-lib/aws-events'; +import * as targets from 'aws-cdk-lib/aws-events-targets'; +import * as ssm from 'aws-cdk-lib/aws-ssm'; +import { RemovalPolicy, Duration } from 'aws-cdk-lib'; + +// Define properties for the main stack +export interface SupportTicketingStackProps extends cdk.StackProps { + envName: string; + vpcCidr: string; + maxAzs: number; + databaseInstanceType: string; + frontendInstanceType: string; + frontendMinCapacity: number; + frontendMaxCapacity: number; + enableSsoIntegration: boolean; + enableKmsEncryption: boolean; +} + +export class SupportTicketingStack extends cdk.Stack { + constructor(scope: Construct, id: string, props: SupportTicketingStackProps) { + super(scope, id, props); + + // KMS Key for encryption (conditionally created) + let dataEncryptionKey: kms.IKey | undefined; + if (props.enableKmsEncryption) { + dataEncryptionKey = new kms.Key(this, 'DataEncryptionKey', { + removalPolicy: RemovalPolicy.DESTROY, // Or RETAIN for production + enableKeyRotation: true, + description: 'KMS key for encrypting application data', + }); + } + + // Cost-allocation tagging mandate (FIN-2231) + cdk.Tags.of(this).add('CostCenter', 'CC-9021'); + cdk.Tags.of(this).add('Owner', 'support-platform'); + cdk.Tags.of(this).add('DataClass', 'customer-support'); + + // VPC Configuration + const vpc = new ec2.Vpc(this, 'SupportVpc', { + cidr: props.vpcCidr, + maxAzs: props.maxAzs, + subnetConfiguration: [ + { + cidrMask: 24, + name: 'PublicSubnet', + subnetType: ec2.SubnetType.PUBLIC, + }, + { + cidrMask: 24, + name: 'PrivateSubnet', + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + ], + }); + + // Security Group for Frontend EC2 Instances + const frontendSg = new ec2.SecurityGroup(this, 'FrontendSg', { + vpc, + description: 'Allow HTTP/HTTPS access to frontend instances', + allowAllOutbound: true, + }); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(80), 'Allow HTTP access'); + frontendSg.addIngressRule(ec2.Peer.anyIpv4(), ec2.Port.tcp(443), 'Allow HTTPS access'); + + // Security Group for Backend ECS Services + const backendSg = new ec2.SecurityGroup(this, 'BackendSg', { + vpc, + description: 'Allow access to backend ECS services from API Gateway and Frontend', + allowAllOutbound: true, + }); + + // Security Group for RDS Database + const rdsSg = new ec2.SecurityGroup(this, 'RdsSg', { + vpc, + description: 'Allow access to RDS from Backend Services', + }); + + // User Database (RDS) + const userDatabase = new rds.DatabaseInstance(this, 'UserDatabase', { + engine: rds.DatabaseInstanceEngine.postgres({ + version: rds.PostgresEngineVersion.VER_14, + }), + instanceType: new ec2.InstanceType(props.databaseInstanceType), + vpc, + vpcSubnets: { + subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS, + }, + securityGroups: [rdsSg], + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN or SNAPSHOT for production + backupRetention: Duration.days(props.envName === 'prod' ? 30 : 7), + multiAz: props.envName === 'prod', + storageEncrypted: props.enableKmsEncryption, + storageEncryptionKey: dataEncryptionKey, + databaseName: 'userdb', + }); + rdsSg.addIngressRule(backendSg, ec2.Port.tcp(5432), 'Allow access from Backend Services'); + + // Ticket Store (DynamoDB) + const ticketTable = new dynamodb.Table(this, 'TicketStore', { + partitionKey: { name: 'ticketId', type: dynamodb.AttributeType.STRING }, + billingMode: dynamodb.BillingMode.PAY_PER_REQUEST, + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + encryption: props.enableKmsEncryption ? dynamodb.TableEncryption.DEFAULT : dynamodb.TableEncryption.AWS_MANAGED, + pointInTimeRecovery: true, + }); + + ticketTable.addGlobalSecondaryIndex({ + indexName: 'byStatus', + partitionKey: { name: 'status', type: dynamodb.AttributeType.STRING }, + sortKey: { name: 'updatedAt', type: dynamodb.AttributeType.STRING }, + projectionType: dynamodb.ProjectionType.KEYS_ONLY, + }); + + // Attachments Store (S3) + const attachmentBucket = new s3.Bucket(this, 'AttachmentStore', { + removalPolicy: RemovalPolicy.DESTROY, // Change to RETAIN for production + autoDeleteObjects: false, // Useful for dev, disable for prod + versioned: true, + encryption: props.enableKmsEncryption ? s3.BucketEncryption.KMS : s3.BucketEncryption.S3_MANAGED, + blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL, + lifecycleRules: [ + { + abortIncompleteMultipartUploadAfter: Duration.days(7), + expiration: Duration.days(365), // Example: delete objects older than 1 year + }, + ], + }); + + // Ticket Queue (SQS) + const ticketQueue = new sqs.Queue(this, 'TicketQueue', { + visibilityTimeout: Duration.seconds(300), + encryption: props.enableKmsEncryption ? sqs.QueueEncryption.KMS : sqs.QueueEncryption.SQS_MANAGED, + encryptionMasterKey: dataEncryptionKey, + deadLetterQueue: { + maxReceiveCount: 5, + queue: new sqs.Queue(this, 'TicketDeadLetterQueue'), + }, + }); + + // Notifications Topic (SNS) + const notificationsTopic = new sns.Topic(this, 'NotificationsTopic', { + masterKey: dataEncryptionKey, // Only if encryption is enabled + }); + + // IAM Role for Lambda Functions + const lambdaExecutionRole = new iam.Role(this, 'LambdaExecutionRole', { + assumedBy: new iam.ServicePrincipal('lambda.amazonaws.com'), + managedPolicies: [ + iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole'), + ], + }); + ticketTable.grantReadWriteData(lambdaExecutionRole); + notificationsTopic.grantPublish(lambdaExecutionRole); + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(lambdaExecutionRole); + } + + // Lambda Functions for Ticket Handling + const ticketCreatorLambda = new lambda.Function(this, 'TicketCreatorLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-creator'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const ticketUpdaterLambda = new lambda.Function(this, 'TicketUpdaterLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/ticket-updater'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + const notificationHandlerLambda = new lambda.Function(this, 'NotificationHandlerLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', // Placeholder + code: lambda.Code.fromAsset('lambda/notification-handler'), // Placeholder directory + role: lambdaExecutionRole, + environment: { + NOTIFICATIONS_TOPIC_ARN: notificationsTopic.topicArn, + }, + }); + + // Event Bus (CloudWatch Events) + const eventBus = new events.EventBus(this, 'AppEventBus', { + eventBusName: 'SupportTicketingEventBus' + }); + eventBus.grantPutEventsTo(lambdaExecutionRole); // Allow lambdas to put events + + // Rule to trigger Notification Handler Lambda from Event Bus + new events.Rule(this, 'NotificationRule', { + eventBus, + eventPattern: { + source: ['custom.ticketing'], // Define your event source + detailType: ['TicketCreated', 'TicketUpdated'], // Define your event types + }, + targets: [new targets.LambdaFunction(notificationHandlerLambda)], + }); + + + // --- Ops alarms --- + const opsTopic = new sns.Topic(this, 'OpsAlertTopic', { topicName: 'SupportOpsAlerts' }); + for (const fn of [ticketCreatorLambda, ticketUpdaterLambda, notificationHandlerLambda]) { + new cdk.aws_cloudwatch.Alarm(this, `${fn.node.id}ErrorAlarm`, { + metric: fn.metricErrors({ period: Duration.minutes(5) }), + threshold: 3, + evaluationPeriods: 2, + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).addAlarmAction(new cdk.aws_cloudwatch_actions.SnsAction(opsTopic)); + } + + // IAM Role for ECS Tasks + const ecsTaskRole = new iam.Role(this, 'EcsTaskRole', { + assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'), + }); + ticketTable.grantReadWriteData(ecsTaskRole); + attachmentBucket.grantReadWrite(ecsTaskRole); + userDatabase.grantConnect(ecsTaskRole, 'appuser'); // Grant DB connect permission + if (dataEncryptionKey) { + dataEncryptionKey.grantEncryptDecrypt(ecsTaskRole); + } + + // Backend Service (ECS Fargate) + const backendService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'BackendService', { + vpc, + taskImageOptions: { + image: ecs.ContainerImage.fromRegistry('amazon/amazon-ecs-sample'), // Replace with your actual image + containerPort: 8080, // Port your application listens on + taskRole: ecsTaskRole, + environment: { + TICKET_TABLE_NAME: ticketTable.tableName, + ATTACHMENT_BUCKET_NAME: attachmentBucket.bucketName, + USER_DB_HOST: userDatabase.dbInstanceEndpointAddress, + USER_DB_PORT: userDatabase.dbInstanceEndpointPort, + USER_DB_NAME: 'userdb', + }, + }, + cpu: 256, + memoryLimitMiB: 512, + desiredCount: props.envName === 'prod' ? 2 : 1, + publicLoadBalancer: false, // Keep backend internal + securityGroups: [backendSg], + listenerPort: 80, // Internal ALB listens on port 80 + }); + backendSg.addIngressRule(frontendSg, ec2.Port.tcp(80), 'Allow from Frontend'); // Allow traffic from Frontend to Backend ALB + // Allow API Gateway to access the backend service (via VPC Link or directly if in same VPC) + // This might require additional setup depending on API Gateway configuration (e.g. private integration) + + // Frontend Service (EC2 with Auto Scaling) + const frontendAutoScalingGroup = new autoscaling.AutoScalingGroup(this, 'FrontendASG', { + vpc, + instanceType: new ec2.InstanceType(props.frontendInstanceType), + machineImage: ec2.MachineImage.latestAmazonLinux2(), // Replace with your AMI + minCapacity: props.frontendMinCapacity, + maxCapacity: props.frontendMaxCapacity, + securityGroup: frontendSg, + vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC }, // Or private if using ALB for frontend too + }); + + // API Gateway + const api = new apigateway.RestApi(this, 'SupportApi', { + restApiName: 'Support Ticketing API', + description: 'API for customer support ticketing system.', + deployOptions: { + stageName: props.envName, + loggingLevel: apigateway.MethodLoggingLevel.INFO, + dataTraceEnabled: true, + }, + endpointConfiguration: { + types: [apigateway.EndpointType.REGIONAL], + }, + defaultCorsPreflightOptions: { + allowOrigins: apigateway.Cors.ALL_ORIGINS, + allowMethods: apigateway.Cors.ALL_METHODS, + allowHeaders: ['Content-Type', 'X-Amz-Date', 'Authorization', 'X-Api-Key', 'X-Amz-Security-Token'], + }, + cloudWatchRole: true, + }); + + // API Gateway Authorizer (Lambda or Cognito - simplified here) + let authorizer: apigateway.IAuthorizer | undefined; + if (props.enableSsoIntegration) { + // Placeholder for Identity Center / Cognito Authorizer or Lambda Authorizer + // For a real setup, you would integrate with AWS SSO (Identity Center) or Cognito User Pools. + // This example uses a mock Lambda authorizer for demonstration. + const authLambda = new lambda.Function(this, 'ApiAuthLambda', { + runtime: lambda.Runtime.NODEJS_18_X, + handler: 'index.handler', + code: lambda.Code.fromInline('exports.handler = async (event) => { return { isAuthorized: true, principalId: \'user\' }; };'), // Mock + }); + authorizer = new apigateway.TokenAuthorizer(this, 'ApiTokenAuthorizer', { + handler: authLambda, + identitySource: apigateway.IdentitySource.header('Authorization'), + }); + } + + // API Gateway resources and integrations + // Example: POST /tickets -> Backend Service (sync) for ticket creation + const ticketsResource = api.root.addResource('tickets'); + const backendIntegration = new apigateway.HttpIntegration(`http://${backendService.loadBalancer.loadBalancerDnsName}/tickets`, { // Assuming /tickets endpoint on backend + httpMethod: 'POST', // Or other methods as needed + options: { + vpcLink: undefined, // If using VPCLink for private integration + } + }); + ticketsResource.addMethod('POST', backendIntegration, { authorizer }); + + // Example: POST /tickets-async -> SQS for async ticket creation + const ticketsAsyncResource = api.root.addResource('tickets-async'); + const sqsIntegration = new apigateway.AwsIntegration({ + service: 'sqs', + path: ticketQueue.queueName, + integrationHttpMethod: 'POST', + options: { + credentialsRole: new iam.Role(this, 'ApiGatewaySqsRole', { // Role for API GW to send to SQS + assumedBy: new iam.ServicePrincipal('apigateway.amazonaws.com'), + }), + requestParameters: { + 'integration.request.header.Content-Type': "'application/x-www-form-urlencoded'", + }, + requestTemplates: { + 'application/json': 'Action=SendMessage&MessageBody=$input.body', + }, + integrationResponses: [ + { + statusCode: '200', + responseTemplates: { + 'application/json': '{"status":"message queued"}', + }, + }, + ], + }, + }); + ticketsAsyncResource.addMethod('POST', sqsIntegration, { + authorizer, + methodResponses: [{ statusCode: '200' }] + }); + + // Grant API Gateway access to backendSg if using VPC Link or if API GW needs direct access to internal ALB + // This depends on specific network setup. If ALB is internal, API GW needs a way to reach it. + // backendSg.addIngressRule(apigateway_principal_for_vpc_link, ec2.Port.tcp(80)); + + + // Systems Manager Parameter Store for Configurations + new ssm.StringParameter(this, 'TicketTableNameParam', { + parameterName: `/support-ticketing/${props.envName}/ticket-table-name`, + stringValue: ticketTable.tableName, + description: 'Name of the DynamoDB table for tickets', + }); + new ssm.StringParameter(this, 'AttachmentBucketNameParam', { + parameterName: `/support-ticketing/${props.envName}/attachment-bucket-name`, + stringValue: attachmentBucket.bucketName, + description: 'Name of the S3 bucket for attachments', + }); + + // CloudWatch Alarms (Example for Ticket Queue depth) + new cdk.CfnOutput(this, 'TicketQueueDepthAlarm', { + value: new cdk.aws_cloudwatch.Alarm(this, 'TicketQueueHighDepthAlarm', { + metric: ticketQueue.metricApproximateNumberOfMessagesVisible(), + threshold: 100, + evaluationPeriods: 2, + alarmDescription: 'Alarm if SQS queue depth is too high', + treatMissingData: cdk.aws_cloudwatch.TreatMissingData.NOT_BREACHING, + }).alarmArn, + }); + + ticketQueue.addToResourcePolicy(new iam.PolicyStatement({ + sid: 'AllowAnySend', + effect: iam.Effect.ALLOW, + principals: [new iam.AnyPrincipal()], + actions: ['sqs:SendMessage'], + resources: [ticketQueue.queueArn], + })); + + // Outputs + new cdk.CfnOutput(this, 'ApiEndpoint', { + value: api.url, + description: 'Endpoint for the Support Ticketing API', + }); + new cdk.CfnOutput(this, 'TicketTableNameOutput', { + value: ticketTable.tableName, + description: 'Name of the Ticket DynamoDB Table', + }); + new cdk.CfnOutput(this, 'AttachmentBucketOutput', { + value: attachmentBucket.bucketName, + description: 'Name of the Attachments S3 Bucket', + }); + if (dataEncryptionKey) { + new cdk.CfnOutput(this, 'KmsKeyArn', { + value: dataEncryptionKey.keyArn, + description: 'ARN of the KMS key used for data encryption', + }); + } + } +} + +const app = new cdk.App(); +const envName = app.node.tryGetContext('env') || 'dev'; +const envConfig = app.node.tryGetContext(envName); + +if (!envConfig) { + throw new Error(`Configuration for environment '${envName}' not found in cdk.json. Available environments: ${Object.keys(app.node.tryGetContext('context') || {}).filter(k => k !== 'env' && typeof app.node.tryGetContext(k) === 'object').join(', ')}`); +} + +const stackProps: SupportTicketingStackProps = { + envName: envName, + vpcCidr: envConfig.vpcCidr || '10.0.0.0/16', + maxAzs: envConfig.maxAzs || 2, + databaseInstanceType: envConfig.databaseInstanceType || 't3.micro', + frontendInstanceType: envConfig.frontendInstanceType || 't3.micro', + frontendMinCapacity: envConfig.frontendMinCapacity || 1, + frontendMaxCapacity: envConfig.frontendMaxCapacity || 2, + enableSsoIntegration: app.node.tryGetContext('featureFlags')?.enableSsoIntegration ?? true, + enableKmsEncryption: app.node.tryGetContext('featureFlags')?.enableKmsEncryption ?? true, + env: { + account: process.env.CDK_DEFAULT_ACCOUNT, + region: process.env.CDK_DEFAULT_REGION, + }, + tags: { + project: app.node.tryGetContext('tags')?.project || 'SupportTicketingSystem', + environment: envName, + department: app.node.tryGetContext('tags')?.department || 'Support', + } +}; + +new SupportTicketingStack(app, `SupportTicketingStack-${envName}`, stackProps); + +app.synth(); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/notification-handler/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/notification-handler/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/notification-handler/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/notification-handler/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/notification-handler/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/notification-handler/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-creator/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-creator/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-creator/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-creator/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-creator/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-creator/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-updater/index.js b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-updater/index.js new file mode 100644 index 00000000..70303fe5 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-updater/index.js @@ -0,0 +1 @@ +exports.handler = async (event) => ({ statusCode: 200 }); diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-updater/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-updater/package.json new file mode 100644 index 00000000..cc1ab05b --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/lambda/ticket-updater/package.json @@ -0,0 +1 @@ +{"name":"placeholder","version":"1.0.0","main":"index.js"} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/package.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/package.json new file mode 100644 index 00000000..7a4808b9 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/package.json @@ -0,0 +1,15 @@ +{ + "name": "support-ticketing-cdk", + "version": "1.0.0", + "private": true, + "scripts": { "synth": "cdk synth" }, + "dependencies": { + "aws-cdk-lib": "2.100.0", + "constructs": "^10.0.0" + }, + "devDependencies": { + "aws-cdk": "2.100.0", + "ts-node": "^10.9.1", + "typescript": "^5.2.2" + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/tsconfig.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/tsconfig.json new file mode 100644 index 00000000..b2bf1575 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/cdk/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "commonjs", + "lib": ["ES2020"], + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "resolveJsonModule": true, + "outDir": "./dist", + "rootDir": "." + }, + "include": ["**/*.ts"], + "exclude": ["node_modules", "cdk.out", "dist"] +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/commit.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/commit.json new file mode 100644 index 00000000..775f34bd --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/commit.json @@ -0,0 +1,20 @@ +{ + "commit_id": "C7", + "sha": "497cdac", + "full_sha": "497cdac25c0e7055c8100399055b1e31b9f529c0", + "parent": "266e7cb", + "message": "docs: add ops runbook", + "author": { + "name": "Priya Raman", + "email": "priya.raman@example.com", + "role": "platform" + }, + "date": "2025-04-15T10:17:00+0000", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "files_changed": [ + "README.md" + ], + "diffstat": "README.md | 5 +++++\n 1 file changed, 5 insertions(+)" +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/diff.patch b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/diff.patch new file mode 100644 index 00000000..cc3fbae8 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/C7/diff.patch @@ -0,0 +1,13 @@ +diff --git a/README.md b/README.md +index 7796468..4ab13dd 100644 +--- a/README.md ++++ b/README.md +@@ -4,3 +4,8 @@ Hybrid AWS platform: 3 Lambda functions, ECS Fargate backend (internal ALB), EC2 + Auto Scaling frontend, DynamoDB, RDS Postgres, S3, SQS + DLQ, SNS, EventBridge, + KMS (conditional), SSM parameters, and an API Gateway with a custom Lambda + TokenAuthorizer. ++ ++## Runbook ++ ++- Alarms publish to `SupportOpsAlerts`; on-call owns triage. ++- Ticket data lives in DynamoDB (PITR on); attachments in S3 (versioned). \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/provenance.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/provenance.json new file mode 100644 index 00000000..884d8586 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/commits/provenance.json @@ -0,0 +1,107 @@ +{ + "C1": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 85, + "sha": "99c3992", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C2": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 89, + "sha": "fdb7953", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C3": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "2edd619", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C4": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "e8a51ad", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C5": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "d97e673", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C6": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "266e7cb", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + }, + "C7": { + "account": "111122223333", + "declared_in_package_json": "2.100.0", + "files": [ + "SupportTicketingStack-dev.assets.json", + "SupportTicketingStack-dev.template.json", + "manifest.json", + "tree.json" + ], + "region": "us-east-1", + "resource_count": 90, + "sha": "497cdac", + "synthesized_with_aws_cdk_lib": "2.150.0", + "version_match": false + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/metadata.yaml b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/metadata.yaml new file mode 100644 index 00000000..97dee9b4 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/metadata.yaml @@ -0,0 +1,121 @@ +benchmark: T7 +family: Trust Boundary Drift +drift_name: SQS Cross-Account +base_repo: text-paste-service/v1 +stack: TextPasteServiceStack-dev +description: "The ticket queue resource policy allows a named account to SendMessage (C3, Mild),\ + \ then any principal (C6, Severe) \u2014 anyone can inject tickets into the processing queue." +independence_note: R1-R5 are independent parallel evolutions of the same v1 stack. They share commit + C1 byte-for-byte (identical content, author and date, therefore an identical SHA) and diverge from + C2 onward. No benchmark depends on any other; each is a separate hypothesis about how v1 could + have evolved. +severity_scale: +- None +- Mild +- Severe +severity_semantics: + commit_severity: drift introduced BY THIS COMMIT + cumulative_severity: standing risk of the tree AT THIS COMMIT + axis: SQS queue policy Principal breadth for SendMessage. +commit_count: 7 +commits: + C1: + sha: 99c3992 + label: V1 + message: 'feat: support ticketing platform baseline' + commit_severity: None + cumulative_severity: None + ground_truth: null + C2: + sha: fdb7953 + label: VB + message: 'chore: add cloudwatch error alarms on lambdas' + commit_severity: None + cumulative_severity: None + ground_truth: null + decoy_rationale: no queue policy change + C3: + sha: 2edd619 + label: V2 + message: 'feat: allow partner account to enqueue tickets' + commit_severity: Mild + cumulative_severity: Mild + ground_truth: + - edge_id: T1 + operation: add + mechanism: sqs_cross_account_named + path: AWS 333344445555 -> sqs:SendMessage -> TicketQueue + controls_present: + - SingleNamedAccount + controls_absent: + - MessageAuthentication + component: TicketQueue + transitive_reach: + - one account can enqueue tickets + construct_ids: + - TicketQueue + template_assertion: AWS::SQS::QueuePolicy with AccountPrincipal 333344445555 and sqs:SendMessage + note: Cross-account enqueue from one named account. Mild. + C4: + sha: e8a51ad + label: VB + message: 'chore: apply cost-allocation tags' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: largest template diff + C5: + sha: d97e673 + label: VB + message: 'feat: add byStatus index on ticket table' + commit_severity: None + cumulative_severity: Mild + ground_truth: null + decoy_rationale: table churn + C6: + sha: 266e7cb + label: V3 + message: 'fix: open ticket queue for any sender' + commit_severity: Severe + cumulative_severity: Severe + ground_truth: + - edge_id: T1 + operation: mutate + mechanism: sqs_public_send + path: AWS anyone -> sqs:SendMessage -> TicketQueue + controls_present: [] + controls_removed: + - SingleNamedAccount + component: TicketQueue + transitive_reach: + - anyone can inject tickets into the processing pipeline + construct_ids: + - TicketQueue + template_assertion: QueuePolicy Principal becomes '*' on sqs:SendMessage + note: Queue-injection attack surface; downstream consumers process forged tickets. + C7: + sha: 497cdac + label: VB + message: 'docs: add ops runbook' + commit_severity: None + cumulative_severity: Severe + ground_truth: null + decoy_rationale: no new drift while cumulative Severe +baseline_known_weaknesses: +- frontendSg 0.0.0.0/0 at C1 +- ticket queue feeds downstream processing +scoring: + drift_commits: + - C3 + - C6 + benign_commits: + - C2 + - C4 + - C5 + - C7 + false_positive_probes: + - C4 + state_vs_event_probes: + - C7 + severity_axis: SQS queue policy Principal breadth for SendMessage + note: New SQS cross-account axis (queue injection). diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/repository b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/repository new file mode 160000 index 00000000..497cdac2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/repository @@ -0,0 +1 @@ +Subproject commit 497cdac25c0e7055c8100399055b1e31b9f529c0 diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/template_analysis.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/template_analysis.json new file mode 100644 index 00000000..f639a9a2 --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/T7/template_analysis.json @@ -0,0 +1,342 @@ +{ + "benchmark": "T7", + "drift_name": "SQS Cross-Account", + "commits": { + "C1": { + "sha": "99c3992", + "label": "V1", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 85, + "template_diff_lines": null, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C2": { + "sha": "fdb7953", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "None", + "resource_count": 89, + "template_diff_lines": 84, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C3": { + "sha": "2edd619", + "label": "V2", + "commit_severity": "Mild", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 40, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C4": { + "sha": "e8a51ad", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 777, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C5": { + "sha": "d97e673", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Mild", + "resource_count": 90, + "template_diff_lines": 52, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C6": { + "sha": "266e7cb", + "label": "V3", + "commit_severity": "Severe", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 15, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + }, + "C7": { + "sha": "497cdac", + "label": "VB", + "commit_severity": "None", + "cumulative_severity": "Severe", + "resource_count": 90, + "template_diff_lines": 0, + "api_methods": [ + { + "logical_id": "SupportApiticketsPOST885ED45B", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "HTTP_PROXY" + }, + { + "logical_id": "SupportApiticketsasyncPOSTABF6C320", + "http_method": "POST", + "authorization_type": "CUSTOM", + "has_authorizer": true, + "integration_type": "AWS" + } + ], + "controls": { + "rest_apis": [ + "SupportApiAECCC4AE" + ], + "authorizers": [ + "ApiTokenAuthorizer9EBE858B" + ], + "lambda_urls": {}, + "public_url_permissions": [], + "load_balancers": { + "BackendServiceLB09608E98": "internal" + }, + "open_ingress_sgs": [ + "BackendServiceLBSecurityGroup2B9BA648", + "FrontendSgE5A146F7" + ], + "wildcard_resource_statements": [], + "wildcard_action_statements": [], + "assume_role_targets": [], + "aws_managed_policies": [ + "service-role/AWSLambdaBasicExecutionRole", + "service-role/AmazonAPIGatewayPushToCloudWatchLogs" + ] + } + } + } +} diff --git a/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/synth_context.json b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/synth_context.json new file mode 100644 index 00000000..2dd8792e --- /dev/null +++ b/ctm-iac/cdk-dataset/support-ticketing-system/versionized_gt/synth_context.json @@ -0,0 +1 @@ +{"env":"dev","dev":{"vpcCidr":"10.0.0.0/16","maxAzs":2,"databaseInstanceType":"t3.micro","frontendInstanceType":"t3.micro","frontendMinCapacity":1,"frontendMaxCapacity":2},"featureFlags":{"enableSsoIntegration":true,"enableKmsEncryption":true}} \ No newline at end of file